2451 lines
75 KiB
TypeScript
2451 lines
75 KiB
TypeScript
/**
|
|
* KRA - Auto Code Generator Page
|
|
* 自动化代码生成页面
|
|
*/
|
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
import {
|
|
Card,
|
|
Table,
|
|
Button,
|
|
Space,
|
|
Form,
|
|
Input,
|
|
Select,
|
|
Checkbox,
|
|
Row,
|
|
Col,
|
|
Drawer,
|
|
Collapse,
|
|
Tooltip,
|
|
message,
|
|
Alert,
|
|
Upload,
|
|
Modal,
|
|
Switch,
|
|
Tag,
|
|
} from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import {
|
|
PlusOutlined,
|
|
DeleteOutlined,
|
|
EditOutlined,
|
|
DownloadOutlined,
|
|
UploadOutlined,
|
|
QuestionCircleOutlined,
|
|
RobotOutlined,
|
|
EyeOutlined,
|
|
SaveOutlined,
|
|
ClearOutlined,
|
|
CodeOutlined,
|
|
HolderOutlined,
|
|
ReloadOutlined,
|
|
FileAddOutlined,
|
|
MenuOutlined,
|
|
} from '@ant-design/icons';
|
|
import { PageContainer } from '@ant-design/pro-components';
|
|
import { useParams, useSearchParams, useNavigate } from '@umijs/max';
|
|
import {
|
|
getDB,
|
|
getTable,
|
|
getColumn,
|
|
createTemp,
|
|
preview,
|
|
getPackage,
|
|
getMeta,
|
|
llmAuto,
|
|
eye,
|
|
getTemplates,
|
|
type AutoCodeParams,
|
|
} from '@/services/kratos/autoCode';
|
|
import { getDict } from '@/utils/dictionary';
|
|
import { getSysDictionaryList } from '@/services/kratos/dictionary';
|
|
import WarningBar from '@/components/WarningBar';
|
|
import CodePreview from './components/CodePreview';
|
|
import styles from './index.less';
|
|
|
|
const { Panel } = Collapse;
|
|
const { TextArea } = Input;
|
|
|
|
// 字段模板
|
|
const fieldTemplate = {
|
|
fieldName: '',
|
|
fieldDesc: '',
|
|
fieldType: '',
|
|
dataType: '',
|
|
fieldJson: '',
|
|
columnName: '',
|
|
dataTypeLong: '',
|
|
comment: '',
|
|
defaultValue: '',
|
|
require: false,
|
|
sort: false,
|
|
form: true,
|
|
desc: true,
|
|
table: true,
|
|
excel: false,
|
|
errorText: '',
|
|
primaryKey: false,
|
|
clearable: true,
|
|
fieldSearchType: '',
|
|
fieldSearchHide: false,
|
|
fieldIndexType: '',
|
|
dictType: '',
|
|
onlyNumber: '',
|
|
dataSource: {
|
|
dbName: '',
|
|
association: 1,
|
|
table: '',
|
|
label: '',
|
|
value: '',
|
|
hasDeletedAt: false,
|
|
},
|
|
};
|
|
|
|
// KRA默认字段列表
|
|
const gormModelList = ['id', 'created_at', 'updated_at', 'deleted_at'];
|
|
const dataModelList = ['created_by', 'updated_by', 'deleted_by'];
|
|
|
|
// 字段类型选项
|
|
const typeOptions = [
|
|
{ label: '字符串', value: 'string' },
|
|
{ label: '富文本', value: 'richtext' },
|
|
{ label: '整型', value: 'int' },
|
|
{ label: '布尔值', value: 'bool' },
|
|
{ label: '浮点型', value: 'float64' },
|
|
{ label: '时间', value: 'time.Time' },
|
|
{ label: '枚举', value: 'enum' },
|
|
{ label: '单图片(字符串)', value: 'picture' },
|
|
{ label: '多图片(json字符串)', value: 'pictures' },
|
|
{ label: '视频(字符串)', value: 'video' },
|
|
{ label: '文件(json字符串)', value: 'file' },
|
|
{ label: 'JSON', value: 'json' },
|
|
{ label: '数组', value: 'array' },
|
|
];
|
|
|
|
// 搜索条件选项
|
|
const typeSearchOptions = [
|
|
{ label: '=', value: '=' },
|
|
{ label: '<>', value: '<>' },
|
|
{ label: '>', value: '>' },
|
|
{ label: '<', value: '<' },
|
|
{ label: 'LIKE', value: 'LIKE' },
|
|
{ label: 'BETWEEN', value: 'BETWEEN' },
|
|
{ label: 'NOT BETWEEN', value: 'NOT BETWEEN' },
|
|
];
|
|
|
|
// 索引类型选项
|
|
const typeIndexOptions = [
|
|
{ label: 'index', value: 'index' },
|
|
{ label: 'uniqueIndex', value: 'uniqueIndex' },
|
|
];
|
|
|
|
// 工具函数
|
|
const toUpperCase = (str: string) => {
|
|
if (!str) return '';
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
};
|
|
|
|
const toLowerCase = (str: string) => {
|
|
if (!str) return '';
|
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
};
|
|
|
|
const toHump = (str: string) => {
|
|
if (!str) return '';
|
|
return str.replace(/_(\w)/g, (_, letter) => letter.toUpperCase());
|
|
};
|
|
|
|
const toSQLLine = (str: string) => {
|
|
if (!str) return '';
|
|
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
|
|
};
|
|
|
|
const getOnlyNumber = () => {
|
|
let randomNumber = '';
|
|
while (randomNumber.length < 16) {
|
|
randomNumber += Math.random().toString(16).substring(2);
|
|
}
|
|
return randomNumber.substring(0, 16);
|
|
};
|
|
|
|
interface FieldItem {
|
|
fieldName: string;
|
|
fieldDesc: string;
|
|
fieldType: string;
|
|
dataType?: string;
|
|
fieldJson: string;
|
|
columnName: string;
|
|
dataTypeLong: string;
|
|
comment: string;
|
|
defaultValue: string;
|
|
require: boolean;
|
|
sort: boolean;
|
|
form: boolean;
|
|
desc: boolean;
|
|
table: boolean;
|
|
excel: boolean;
|
|
errorText?: string;
|
|
primaryKey: boolean;
|
|
clearable?: boolean;
|
|
fieldSearchType: string;
|
|
fieldSearchHide?: boolean;
|
|
fieldIndexType: string;
|
|
dictType?: string;
|
|
onlyNumber?: string;
|
|
disabled?: boolean;
|
|
dataSource?: {
|
|
dbName: string;
|
|
association: number;
|
|
table: string;
|
|
label: string;
|
|
value: string;
|
|
hasDeletedAt: boolean;
|
|
};
|
|
}
|
|
|
|
interface FormData {
|
|
structName: string;
|
|
tableName: string;
|
|
packageName: string;
|
|
package: string;
|
|
abbreviation: string;
|
|
description: string;
|
|
businessDB: string;
|
|
autoCreateApiToSql: boolean;
|
|
autoCreateMenuToSql: boolean;
|
|
autoCreateBtnAuth: boolean;
|
|
autoMigrate: boolean;
|
|
gvaModel: boolean;
|
|
autoCreateResource: boolean;
|
|
onlyTemplate: boolean;
|
|
isTree: boolean;
|
|
generateWeb: boolean;
|
|
generateServer: boolean;
|
|
treeJson: string;
|
|
humpPackageName?: string;
|
|
templateType: string; // 模板类型:package 或 plugin
|
|
fields: FieldItem[];
|
|
}
|
|
|
|
const AutoCodePage: React.FC = () => {
|
|
const params = useParams();
|
|
const [searchParams] = useSearchParams();
|
|
const navigate = useNavigate();
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [prompt, setPrompt] = useState('');
|
|
const [isAdd, setIsAdd] = useState(false);
|
|
|
|
// 数据库选择
|
|
const [dbList, setDbList] = useState<any[]>([]);
|
|
const [dbOptions, setDbOptions] = useState<any[]>([]);
|
|
const [tableOptions, setTableOptions] = useState<any[]>([]);
|
|
const [dbform, setDbform] = useState({
|
|
businessDB: '',
|
|
dbName: '',
|
|
tableName: '',
|
|
});
|
|
|
|
// 模板列表
|
|
const [pkgs, setPkgs] = useState<any[]>([]);
|
|
|
|
// 模板类型选项
|
|
const [templateTypes, setTemplateTypes] = useState<string[]>(['package', 'plugin']);
|
|
|
|
// 字段类型映射
|
|
const [fdMap, setFdMap] = useState<Record<string, string>>({});
|
|
|
|
// 表单数据
|
|
const [form, setForm] = useState<FormData>({
|
|
structName: '',
|
|
tableName: '',
|
|
packageName: '',
|
|
package: '',
|
|
abbreviation: '',
|
|
description: '',
|
|
businessDB: '',
|
|
autoCreateApiToSql: true,
|
|
autoCreateMenuToSql: true,
|
|
autoCreateBtnAuth: false,
|
|
autoMigrate: true,
|
|
gvaModel: true,
|
|
autoCreateResource: false,
|
|
onlyTemplate: false,
|
|
isTree: false,
|
|
generateWeb: true,
|
|
generateServer: true,
|
|
treeJson: '',
|
|
templateType: 'package', // 默认使用 package 模板
|
|
fields: [],
|
|
});
|
|
|
|
// 字段编辑弹窗
|
|
const [fieldDialogVisible, setFieldDialogVisible] = useState(false);
|
|
const [editingField, setEditingField] = useState<FieldItem | null>(null);
|
|
const [addFlag, setAddFlag] = useState<'add' | 'edit'>('add');
|
|
const [dialogMiddle, setDialogMiddle] = useState<FieldItem>({ ...fieldTemplate } as FieldItem);
|
|
const [bk, setBk] = useState<FieldItem | null>(null);
|
|
|
|
// 预览弹窗
|
|
const [previewVisible, setPreviewVisible] = useState(false);
|
|
const [previewCode, setPreviewCode] = useState<any>(null);
|
|
|
|
// TextArea焦点状态
|
|
const [textareaFocused, setTextareaFocused] = useState(false);
|
|
|
|
// 字典选项列表
|
|
const [dictOptions, setDictOptions] = useState<any[]>([]);
|
|
|
|
// 数据源相关状态
|
|
const [dbTableList, setDbTableList] = useState<any[]>([]);
|
|
const [dbColumnList, setDbColumnList] = useState<any[]>([]);
|
|
const [dataSourceCollapsed, setDataSourceCollapsed] = useState<string[]>([]);
|
|
|
|
// 键盘快捷键处理 - Ctrl+Enter 或 Cmd+Enter 直接生成
|
|
const handleKeydown = useCallback((event: KeyboardEvent) => {
|
|
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
|
|
llmAutoFunc();
|
|
}
|
|
}, []);
|
|
|
|
// 剪贴板粘贴图片处理
|
|
const handlePaste = useCallback(async (event: Event) => {
|
|
const clipboardEvent = event as ClipboardEvent;
|
|
const items = clipboardEvent.clipboardData?.items;
|
|
if (!items) return;
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
if (items[i].type.indexOf('image') !== -1) {
|
|
const file = items[i].getAsFile();
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = async (e) => {
|
|
const base64String = e.target?.result as string;
|
|
try {
|
|
const res = await eye({ picture: base64String, command: 'eye' });
|
|
if (res.code === 0) {
|
|
setPrompt(res.data);
|
|
llmAutoFunc();
|
|
}
|
|
} catch (error) {
|
|
message.error('识图失败');
|
|
}
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
// TextArea 焦点处理
|
|
const handleFocus = () => {
|
|
setTextareaFocused(true);
|
|
document.addEventListener('keydown', handleKeydown);
|
|
document.addEventListener('paste', handlePaste);
|
|
};
|
|
|
|
const handleBlur = () => {
|
|
setTextareaFocused(false);
|
|
document.removeEventListener('keydown', handleKeydown);
|
|
document.removeEventListener('paste', handlePaste);
|
|
};
|
|
|
|
// 清理事件监听
|
|
useEffect(() => {
|
|
return () => {
|
|
document.removeEventListener('keydown', handleKeydown);
|
|
document.removeEventListener('paste', handlePaste);
|
|
};
|
|
}, [handleKeydown, handlePaste]);
|
|
|
|
// 初始化
|
|
useEffect(() => {
|
|
init();
|
|
}, []);
|
|
|
|
// 监听generateServer变化
|
|
useEffect(() => {
|
|
if (!form.generateServer) {
|
|
setForm(prev => ({
|
|
...prev,
|
|
autoCreateApiToSql: false,
|
|
autoMigrate: false,
|
|
}));
|
|
}
|
|
}, [form.generateServer]);
|
|
|
|
// 监听generateWeb变化
|
|
useEffect(() => {
|
|
if (!form.generateWeb) {
|
|
setForm(prev => ({
|
|
...prev,
|
|
autoCreateMenuToSql: false,
|
|
autoCreateBtnAuth: false,
|
|
}));
|
|
}
|
|
}, [form.generateWeb]);
|
|
|
|
const init = async () => {
|
|
await getDbFunc();
|
|
await setFdMapFunc();
|
|
await getPkgs();
|
|
await getDictOptionsFunc();
|
|
await getTemplateTypesFunc();
|
|
|
|
// 从路由获取ID
|
|
const id = params.id;
|
|
if (id) {
|
|
await getAutoCodeJson(Number(id));
|
|
}
|
|
|
|
// 获取缓存
|
|
getCatch();
|
|
};
|
|
|
|
// 获取模板类型列表
|
|
const getTemplateTypesFunc = async () => {
|
|
try {
|
|
const res = await getTemplates();
|
|
if (res.code === 0 && res.data) {
|
|
setTemplateTypes(res.data || ['package', 'plugin']);
|
|
}
|
|
} catch (error) {
|
|
console.error('获取模板类型失败:', error);
|
|
}
|
|
};
|
|
|
|
// 获取字典选项列表
|
|
const getDictOptionsFunc = async () => {
|
|
try {
|
|
const res = await getSysDictionaryList({ page: 1, pageSize: 999999 } as any);
|
|
if (res.code === 0 && res.data) {
|
|
setDictOptions(res.data.list || res.data || []);
|
|
}
|
|
} catch (error) {
|
|
console.error('获取字典列表失败:', error);
|
|
}
|
|
};
|
|
|
|
// 获取数据库列表
|
|
const getDbFunc = async () => {
|
|
try {
|
|
const res = await getDB({ businessDB: dbform.businessDB });
|
|
if (res.code === 0 && res.data) {
|
|
setDbOptions(res.data.dbs || []);
|
|
setDbList(res.data.dbList || []);
|
|
}
|
|
} catch (error) {
|
|
console.error('获取数据库失败:', error);
|
|
}
|
|
};
|
|
|
|
// 获取表列表
|
|
const getTableFunc = async () => {
|
|
try {
|
|
const res = await getTable({
|
|
businessDB: dbform.businessDB,
|
|
dbName: dbform.dbName,
|
|
});
|
|
if (res.code === 0 && res.data) {
|
|
setTableOptions(res.data.tables || []);
|
|
}
|
|
setDbform(prev => ({ ...prev, tableName: '' }));
|
|
} catch (error) {
|
|
console.error('获取表列表失败:', error);
|
|
}
|
|
};
|
|
|
|
// 获取字段列表
|
|
const getColumnFunc = async () => {
|
|
if (!dbform.dbName || !dbform.tableName) {
|
|
message.warning('请选择数据库和表');
|
|
return;
|
|
}
|
|
try {
|
|
const res = await getColumn(dbform);
|
|
if (res.code === 0 && res.data) {
|
|
let dbtype = '';
|
|
if (dbform.businessDB) {
|
|
const dbtmp = dbList.find(item => item.aliasName === dbform.businessDB);
|
|
if (dbtmp) {
|
|
dbtype = dbtmp.dbtype;
|
|
}
|
|
}
|
|
|
|
const tbHump = toHump(dbform.tableName);
|
|
const newFields: FieldItem[] = [];
|
|
|
|
res.data.columns?.forEach((item: any) => {
|
|
if (needAppend(item)) {
|
|
const fbHump = toHump(item.columnName);
|
|
newFields.push({
|
|
onlyNumber: getOnlyNumber(),
|
|
fieldName: toUpperCase(fbHump),
|
|
fieldDesc: item.columnComment || fbHump + '字段',
|
|
fieldType: fdMap[item.dataType] || 'string',
|
|
dataType: item.dataType,
|
|
fieldJson: fbHump,
|
|
primaryKey: item.primaryKey || false,
|
|
dataTypeLong: item.dataTypeLong?.split(',')[0] || '',
|
|
columnName: dbtype === 'oracle' ? item.columnName.toUpperCase() : item.columnName,
|
|
comment: item.columnComment || '',
|
|
defaultValue: '',
|
|
require: false,
|
|
errorText: '',
|
|
clearable: true,
|
|
fieldSearchType: '',
|
|
fieldIndexType: '',
|
|
dictType: '',
|
|
form: true,
|
|
table: true,
|
|
excel: false,
|
|
desc: true,
|
|
sort: false,
|
|
dataSource: {
|
|
dbName: '',
|
|
association: 1,
|
|
table: '',
|
|
label: '',
|
|
value: '',
|
|
hasDeletedAt: false,
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
setForm(prev => ({
|
|
...prev,
|
|
gvaModel: false,
|
|
structName: toUpperCase(tbHump),
|
|
tableName: dbform.tableName,
|
|
packageName: toLowerCase(tbHump),
|
|
abbreviation: toLowerCase(tbHump),
|
|
description: tbHump + '表',
|
|
autoCreateApiToSql: true,
|
|
generateServer: true,
|
|
generateWeb: true,
|
|
fields: newFields,
|
|
}));
|
|
|
|
message.success('字段获取成功');
|
|
}
|
|
} catch (error) {
|
|
message.error('获取字段失败');
|
|
}
|
|
};
|
|
|
|
// 判断是否需要添加字段
|
|
const needAppend = (item: any) => {
|
|
if (form.gvaModel && gormModelList.includes(item.columnName)) {
|
|
return false;
|
|
}
|
|
if (form.autoCreateResource && dataModelList.includes(item.columnName)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// 设置字段类型映射
|
|
const setFdMapFunc = async () => {
|
|
const fdTypes = ['string', 'int', 'bool', 'float64', 'time.Time'];
|
|
const newFdMap: Record<string, string> = {};
|
|
|
|
for (const fdtype of fdTypes) {
|
|
try {
|
|
const res = await getDict(fdtype);
|
|
res?.forEach((item: any) => {
|
|
newFdMap[item.label] = fdtype;
|
|
});
|
|
} catch (error) {
|
|
// ignore
|
|
}
|
|
}
|
|
setFdMap(newFdMap);
|
|
};
|
|
|
|
// 获取模板列表
|
|
const getPkgs = async () => {
|
|
try {
|
|
const res = await getPackage();
|
|
if (res.code === 0 && res.data) {
|
|
setPkgs(res.data.pkgs || []);
|
|
}
|
|
} catch (error) {
|
|
console.error('获取模板列表失败:', error);
|
|
}
|
|
};
|
|
|
|
// 获取历史代码配置
|
|
const getAutoCodeJson = async (id: number) => {
|
|
try {
|
|
const res = await getMeta({ id });
|
|
if (res.code === 0 && res.data) {
|
|
const add = searchParams.get('isAdd');
|
|
setIsAdd(!!add);
|
|
const meta = JSON.parse(res.data.meta);
|
|
if (add) {
|
|
meta.fields?.forEach((item: FieldItem) => {
|
|
item.disabled = true;
|
|
});
|
|
}
|
|
setForm(meta);
|
|
}
|
|
} catch (error) {
|
|
console.error('获取配置失败:', error);
|
|
}
|
|
};
|
|
|
|
// AI生成
|
|
const llmAutoFunc = async (flag?: boolean) => {
|
|
if (flag && !form.structName) {
|
|
message.error('请输入结构体名称');
|
|
return;
|
|
}
|
|
if (!flag && !prompt) {
|
|
message.error('请输入描述');
|
|
return;
|
|
}
|
|
|
|
if (form.fields.length > 0) {
|
|
Modal.confirm({
|
|
title: '提示',
|
|
content: 'AI生成会清空当前数据,是否继续?',
|
|
onOk: async () => {
|
|
await doLlmAuto(flag);
|
|
},
|
|
});
|
|
} else {
|
|
await doLlmAuto(flag);
|
|
}
|
|
};
|
|
|
|
const doLlmAuto = async (flag?: boolean) => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await llmAuto({
|
|
prompt: flag ? '结构体名称为' + form.structName : prompt,
|
|
});
|
|
if (res.code === 0) {
|
|
const json = JSON.parse(res.data);
|
|
json.fields?.forEach((item: FieldItem) => {
|
|
item.fieldName = toUpperCase(item.fieldName);
|
|
});
|
|
|
|
setForm(prev => ({
|
|
...prev,
|
|
...json,
|
|
generateServer: true,
|
|
generateWeb: true,
|
|
fields: json.fields || [],
|
|
}));
|
|
message.success('AI生成成功');
|
|
}
|
|
} catch (error) {
|
|
message.error('AI生成失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 识图功能
|
|
const eyeFunc = () => {
|
|
const input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.accept = 'image/*';
|
|
|
|
input.onchange = async (event: any) => {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = async (e) => {
|
|
const base64String = e.target?.result as string;
|
|
try {
|
|
const res = await eye({ picture: base64String, command: 'eye' });
|
|
if (res.code === 0) {
|
|
setPrompt(res.data);
|
|
llmAutoFunc();
|
|
}
|
|
} catch (error) {
|
|
message.error('识图失败');
|
|
}
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
};
|
|
|
|
input.click();
|
|
};
|
|
|
|
// 使用KRA结构
|
|
const useKraModel = (checked: boolean) => {
|
|
if (checked && form.fields.length > 0) {
|
|
Modal.confirm({
|
|
title: '注意',
|
|
content: '如果您开启KRA默认结构,会自动添加ID,CreatedAt,UpdatedAt,DeletedAt字段,此行为将自动清除您目前在下方创建的重名字段,是否继续?',
|
|
onOk: () => {
|
|
setForm(prev => ({
|
|
...prev,
|
|
gvaModel: true,
|
|
fields: prev.fields.filter(item => !gormModelList.includes(item.columnName)),
|
|
}));
|
|
},
|
|
onCancel: () => {
|
|
setForm(prev => ({ ...prev, gvaModel: false }));
|
|
},
|
|
});
|
|
} else {
|
|
setForm(prev => ({ ...prev, gvaModel: checked }));
|
|
}
|
|
};
|
|
|
|
// 自动填充字段Json和数据库字段
|
|
const autoFillField = () => {
|
|
if (!dialogMiddle.fieldName) {
|
|
message.warning('请先输入字段名称');
|
|
return;
|
|
}
|
|
const fieldJson = toLowerCase(dialogMiddle.fieldName);
|
|
const columnName = toSQLLine(fieldJson);
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
fieldJson,
|
|
columnName,
|
|
}));
|
|
};
|
|
|
|
// 清除字段类型相关选项
|
|
const clearFieldTypeOptions = () => {
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
fieldSearchType: '',
|
|
dictType: '',
|
|
}));
|
|
};
|
|
|
|
// 数据源关联模式变更
|
|
const handleAssociationChange = (val: number) => {
|
|
if (val === 2) {
|
|
Modal.confirm({
|
|
title: '提示',
|
|
content: '一对多关联模式下,数据类型会改变为数组,后端表现为json,具体表现为数组模式,是否继续?',
|
|
onOk: () => {
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
fieldType: 'array',
|
|
dataSource: { ...prev.dataSource!, association: 2 },
|
|
}));
|
|
},
|
|
onCancel: () => {
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: { ...prev.dataSource!, association: 1 },
|
|
}));
|
|
},
|
|
});
|
|
} else {
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: { ...prev.dataSource!, association: val },
|
|
}));
|
|
}
|
|
};
|
|
|
|
// 获取数据源表列表
|
|
const getDBTableListFunc = async (dbName?: string) => {
|
|
try {
|
|
const res = await getTable({ businessDB: dbName || dialogMiddle.dataSource?.dbName || '' });
|
|
if (res.code === 0 && res.data) {
|
|
setDbTableList((res.data.tables || []).map((item: any) => ({
|
|
tableName: item.tableName,
|
|
value: item.tableName,
|
|
})));
|
|
}
|
|
} catch (error) {
|
|
console.error('获取表列表失败:', error);
|
|
}
|
|
};
|
|
|
|
// 选择数据源表
|
|
const selectDataSourceTable = async (tableName: string, isInit?: boolean) => {
|
|
if (!tableName) {
|
|
setDbColumnList([]);
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: {
|
|
...prev.dataSource!,
|
|
table: '',
|
|
label: '',
|
|
value: '',
|
|
hasDeletedAt: false,
|
|
},
|
|
}));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const res = await getColumn({
|
|
businessDB: dialogMiddle.dataSource?.dbName || '',
|
|
tableName,
|
|
});
|
|
if (res.code === 0 && res.data) {
|
|
let hasDeletedAt = false;
|
|
const columns = (res.data.columns || []).map((item: any) => {
|
|
if (item.columnName === 'deleted_at') {
|
|
hasDeletedAt = true;
|
|
}
|
|
return {
|
|
columnName: item.columnName,
|
|
value: item.columnName,
|
|
type: item.dataType,
|
|
isPrimary: item.primaryKey,
|
|
comment: item.columnComment,
|
|
};
|
|
});
|
|
setDbColumnList(columns);
|
|
|
|
if (!isInit && columns.length > 0) {
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: {
|
|
...prev.dataSource!,
|
|
table: tableName,
|
|
label: columns[0].columnName,
|
|
value: columns[0].columnName,
|
|
hasDeletedAt,
|
|
},
|
|
}));
|
|
} else {
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: {
|
|
...prev.dataSource!,
|
|
table: tableName,
|
|
hasDeletedAt,
|
|
},
|
|
}));
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('获取字段列表失败:', error);
|
|
}
|
|
};
|
|
|
|
// 数据源数据库变更
|
|
const handleDataSourceDbChange = (dbName: string) => {
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: {
|
|
...prev.dataSource!,
|
|
dbName,
|
|
table: '',
|
|
label: '',
|
|
value: '',
|
|
},
|
|
}));
|
|
setDbColumnList([]);
|
|
if (dbName) {
|
|
getDBTableListFunc(dbName);
|
|
}
|
|
};
|
|
|
|
// 清除数据源选择
|
|
const clearDataSourceAccess = () => {
|
|
setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: {
|
|
...prev.dataSource!,
|
|
label: '',
|
|
value: '',
|
|
},
|
|
}));
|
|
};
|
|
|
|
// 拖拽排序处理
|
|
const moveField = (dragIndex: number, hoverIndex: number) => {
|
|
const newFields = [...form.fields];
|
|
const [draggedItem] = newFields.splice(dragIndex, 1);
|
|
newFields.splice(hoverIndex, 0, draggedItem);
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
};
|
|
|
|
|
|
// 新增/编辑字段
|
|
const editAndAddField = (item?: FieldItem) => {
|
|
setFieldDialogVisible(true);
|
|
if (item) {
|
|
setAddFlag('edit');
|
|
if (!item.dataSource) {
|
|
item.dataSource = {
|
|
dbName: '',
|
|
association: 1,
|
|
table: '',
|
|
label: '',
|
|
value: '',
|
|
hasDeletedAt: false,
|
|
};
|
|
}
|
|
setBk(JSON.parse(JSON.stringify(item)));
|
|
setDialogMiddle(item);
|
|
// 如果有数据源表,加载字段列表
|
|
if (item.dataSource?.table) {
|
|
getDBTableListFunc(item.dataSource.dbName);
|
|
selectDataSourceTable(item.dataSource.table, true);
|
|
}
|
|
} else {
|
|
setAddFlag('add');
|
|
const newField = {
|
|
...fieldTemplate,
|
|
onlyNumber: getOnlyNumber(),
|
|
} as FieldItem;
|
|
setDialogMiddle(newField);
|
|
setDbTableList([]);
|
|
setDbColumnList([]);
|
|
}
|
|
};
|
|
|
|
// 确认字段编辑
|
|
const enterDialog = () => {
|
|
if (!dialogMiddle.fieldName) {
|
|
message.error('请输入字段名称');
|
|
return;
|
|
}
|
|
|
|
const updatedField = {
|
|
...dialogMiddle,
|
|
fieldName: toUpperCase(dialogMiddle.fieldName),
|
|
};
|
|
|
|
if (addFlag === 'add') {
|
|
setForm(prev => ({
|
|
...prev,
|
|
fields: [...prev.fields, updatedField],
|
|
}));
|
|
} else {
|
|
setForm(prev => ({
|
|
...prev,
|
|
fields: prev.fields.map(f =>
|
|
f.onlyNumber === updatedField.onlyNumber ? updatedField : f
|
|
),
|
|
}));
|
|
}
|
|
setFieldDialogVisible(false);
|
|
};
|
|
|
|
// 关闭字段编辑
|
|
const closeDialog = () => {
|
|
if (addFlag === 'edit' && bk) {
|
|
// 恢复备份
|
|
setForm(prev => ({
|
|
...prev,
|
|
fields: prev.fields.map(f =>
|
|
f.onlyNumber === bk.onlyNumber ? bk : f
|
|
),
|
|
}));
|
|
}
|
|
setFieldDialogVisible(false);
|
|
};
|
|
|
|
// 删除字段
|
|
const deleteField = (index: number) => {
|
|
Modal.confirm({
|
|
title: '提示',
|
|
content: '确定要删除吗?',
|
|
onOk: () => {
|
|
setForm(prev => ({
|
|
...prev,
|
|
fields: prev.fields.filter((_, i) => i !== index),
|
|
}));
|
|
},
|
|
});
|
|
};
|
|
|
|
// 暂存
|
|
const catchData = () => {
|
|
sessionStorage.setItem('autoCode', JSON.stringify(form));
|
|
message.success('暂存成功');
|
|
};
|
|
|
|
// 获取缓存
|
|
const getCatch = () => {
|
|
const data = sessionStorage.getItem('autoCode');
|
|
if (data) {
|
|
try {
|
|
setForm(JSON.parse(data));
|
|
} catch (error) {
|
|
// ignore
|
|
}
|
|
}
|
|
};
|
|
|
|
// 清除缓存
|
|
const clearCatch = () => {
|
|
setForm({
|
|
structName: '',
|
|
tableName: '',
|
|
packageName: '',
|
|
package: '',
|
|
abbreviation: '',
|
|
description: '',
|
|
businessDB: '',
|
|
autoCreateApiToSql: true,
|
|
autoCreateMenuToSql: true,
|
|
autoCreateBtnAuth: false,
|
|
autoMigrate: true,
|
|
gvaModel: true,
|
|
autoCreateResource: false,
|
|
onlyTemplate: false,
|
|
isTree: false,
|
|
generateWeb: true,
|
|
generateServer: true,
|
|
treeJson: '',
|
|
templateType: 'package',
|
|
fields: [],
|
|
});
|
|
sessionStorage.removeItem('autoCode');
|
|
message.success('清除成功');
|
|
};
|
|
|
|
// 导出JSON
|
|
const exportJson = () => {
|
|
const dataStr = JSON.stringify(form, null, 2);
|
|
const blob = new Blob([dataStr], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'form_data.json';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
// 导入JSON
|
|
const importJson = (file: File) => {
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
try {
|
|
const data = JSON.parse(e.target?.result as string);
|
|
setForm({
|
|
...data,
|
|
generateServer: true,
|
|
generateWeb: true,
|
|
});
|
|
message.success('JSON文件导入成功');
|
|
} catch (error) {
|
|
message.error('无效的JSON文件');
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
return false;
|
|
};
|
|
|
|
// 验证表单
|
|
const validateForm = (): boolean => {
|
|
if (form.isTree && !form.treeJson) {
|
|
message.error('请填写树型结构的前端展示json属性');
|
|
return false;
|
|
}
|
|
if (!form.generateWeb && !form.generateServer) {
|
|
message.error('请至少选择一个生成项');
|
|
return false;
|
|
}
|
|
if (!form.onlyTemplate) {
|
|
if (form.fields.length <= 0) {
|
|
message.error('请填写至少一个field');
|
|
return false;
|
|
}
|
|
if (!form.gvaModel && form.fields.every(item => !item.primaryKey)) {
|
|
message.error('您至少需要创建一个主键才能保证自动化代码的可行性');
|
|
return false;
|
|
}
|
|
if (form.fields.some(item => item.fieldName === form.structName)) {
|
|
message.error('存在与结构体同名的字段');
|
|
return false;
|
|
}
|
|
if (form.fields.some(item => item.fieldJson === form.package)) {
|
|
message.error('存在与模板同名的字段JSON');
|
|
return false;
|
|
}
|
|
if (form.fields.some(item => !item.fieldType)) {
|
|
message.error('请填写所有字段类型后进行提交');
|
|
return false;
|
|
}
|
|
if (form.package === form.abbreviation) {
|
|
message.error('package和结构体简称不可同名');
|
|
return false;
|
|
}
|
|
}
|
|
if (!form.structName) {
|
|
message.error('请输入结构体名称');
|
|
return false;
|
|
}
|
|
if (!form.abbreviation) {
|
|
message.error('请输入结构体简称');
|
|
return false;
|
|
}
|
|
if (!form.description) {
|
|
message.error('请输入结构体描述');
|
|
return false;
|
|
}
|
|
if (!form.packageName) {
|
|
message.error('请输入文件名称');
|
|
return false;
|
|
}
|
|
if (!form.package) {
|
|
message.error('请选择模板');
|
|
return false;
|
|
}
|
|
if (form.structName === form.abbreviation) {
|
|
message.error('structName和struct简称不能相同');
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// 提交表单(预览或生成)
|
|
const enterForm = async (isPreview: boolean) => {
|
|
if (!validateForm()) return;
|
|
|
|
// 处理表单数据
|
|
const submitForm = { ...form };
|
|
submitForm.structName = toUpperCase(submitForm.structName.trim());
|
|
submitForm.tableName = submitForm.tableName.replace(' ', '');
|
|
if (!submitForm.tableName) {
|
|
submitForm.tableName = toSQLLine(toLowerCase(submitForm.structName));
|
|
}
|
|
submitForm.humpPackageName = toSQLLine(submitForm.packageName);
|
|
|
|
// 处理字段
|
|
submitForm.fields = submitForm.fields.map(item => {
|
|
const newItem = { ...item };
|
|
newItem.fieldName = toUpperCase(newItem.fieldName);
|
|
if (newItem.fieldType === 'enum') {
|
|
newItem.dataTypeLong = newItem.dataTypeLong.replace(/[\[\]{}()]/g, '');
|
|
const arr = newItem.dataTypeLong.split(',');
|
|
newItem.dataTypeLong = arr.map(ele =>
|
|
ele.indexOf("'") === -1 ? `'${ele}'` : ele
|
|
).join(',');
|
|
}
|
|
return newItem;
|
|
});
|
|
|
|
setLoading(true);
|
|
try {
|
|
if (isPreview) {
|
|
const res = await preview({
|
|
...submitForm,
|
|
isAdd: isAdd,
|
|
fields: submitForm.fields.filter(item => !item.disabled),
|
|
} as AutoCodeParams);
|
|
if (res.code === 0 && res.data) {
|
|
setPreviewCode(res.data.autoCode || res.data);
|
|
setPreviewVisible(true);
|
|
}
|
|
} else {
|
|
const res = await createTemp(submitForm as AutoCodeParams);
|
|
if (res.code === 0) {
|
|
message.success('自动化代码创建成功,自动移动成功');
|
|
clearCatch();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
message.error(isPreview ? '预览失败' : '生成失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 判断搜索条件是否可选
|
|
const canSelect = (fieldType: string, item: string) => {
|
|
if (fieldType === 'richtext') {
|
|
return item !== 'LIKE';
|
|
}
|
|
if (fieldType !== 'string' && item === 'LIKE') {
|
|
return true;
|
|
}
|
|
const nonNumericTypes = ['int', 'time.Time', 'float64'];
|
|
if (!nonNumericTypes.includes(fieldType) && ['BETWEEN', 'NOT BETWEEN'].includes(item)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// 跳转到模板管理
|
|
const goPkgs = () => {
|
|
navigate('/systemTools/autoPkg');
|
|
};
|
|
|
|
|
|
// 字段表格列定义
|
|
const fieldColumns: ColumnsType<FieldItem> = [
|
|
{
|
|
title: '',
|
|
key: 'drag',
|
|
width: 50,
|
|
render: (_, __, index) => (
|
|
<Tooltip title="拖拽排序">
|
|
<MenuOutlined
|
|
className={styles.dragHandle}
|
|
style={{ cursor: 'move', color: '#999' }}
|
|
/>
|
|
</Tooltip>
|
|
),
|
|
},
|
|
{ title: '序列', key: 'index', width: 60, render: (_, __, index) => index + 1 },
|
|
{
|
|
title: '主键',
|
|
dataIndex: 'primaryKey',
|
|
width: 60,
|
|
render: (val, record, index) => (
|
|
<Checkbox
|
|
checked={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].primaryKey = e.target.checked;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '字段名称',
|
|
dataIndex: 'fieldName',
|
|
width: 140,
|
|
render: (val) => <Input value={val} disabled />,
|
|
},
|
|
{
|
|
title: '中文名',
|
|
dataIndex: 'fieldDesc',
|
|
width: 140,
|
|
render: (val, record, index) => (
|
|
<Input
|
|
value={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].fieldDesc = e.target.value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '默认值',
|
|
dataIndex: 'defaultValue',
|
|
width: 120,
|
|
render: (val, record, index) => (
|
|
<Input
|
|
value={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].defaultValue = e.target.value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '必填',
|
|
dataIndex: 'require',
|
|
width: 60,
|
|
render: (val, record, index) => (
|
|
<Checkbox
|
|
checked={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].require = e.target.checked;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '排序',
|
|
dataIndex: 'sort',
|
|
width: 60,
|
|
render: (val, record, index) => (
|
|
<Checkbox
|
|
checked={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].sort = e.target.checked;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '新建/编辑',
|
|
dataIndex: 'form',
|
|
width: 90,
|
|
render: (val, record, index) => (
|
|
<Checkbox
|
|
checked={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].form = e.target.checked;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '表格',
|
|
dataIndex: 'table',
|
|
width: 60,
|
|
render: (val, record, index) => (
|
|
<Checkbox
|
|
checked={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].table = e.target.checked;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '详情',
|
|
dataIndex: 'desc',
|
|
width: 60,
|
|
render: (val, record, index) => (
|
|
<Checkbox
|
|
checked={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].desc = e.target.checked;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
...(!isAdd ? [{
|
|
title: '导入/导出',
|
|
dataIndex: 'excel',
|
|
width: 90,
|
|
render: (val: boolean, record: FieldItem, index: number) => (
|
|
<Checkbox
|
|
checked={val}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].excel = e.target.checked;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
}] : []),
|
|
{
|
|
title: '字段Json',
|
|
dataIndex: 'fieldJson',
|
|
width: 140,
|
|
render: (val, record, index) => (
|
|
<Input
|
|
value={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].fieldJson = e.target.value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '字段类型',
|
|
dataIndex: 'fieldType',
|
|
width: 140,
|
|
render: (val, record, index) => (
|
|
<Select
|
|
value={val}
|
|
disabled={record.disabled}
|
|
style={{ width: '100%' }}
|
|
options={typeOptions}
|
|
onChange={(value) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].fieldType = value;
|
|
// 清除相关选项
|
|
newFields[index].fieldSearchType = '';
|
|
newFields[index].dictType = '';
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '字典类型',
|
|
dataIndex: 'dictType',
|
|
width: 140,
|
|
render: (val, record, index) => (
|
|
<Select
|
|
value={val}
|
|
disabled={record.disabled || (record.fieldType !== 'string' && record.fieldType !== 'array')}
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
placeholder="选择字典"
|
|
onChange={(value) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].dictType = value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
>
|
|
{dictOptions.map((dict: any) => (
|
|
<Select.Option key={dict.type} value={dict.type}>
|
|
{`${dict.type}(${dict.name})`}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
),
|
|
},
|
|
{
|
|
title: '索引类型',
|
|
dataIndex: 'fieldIndexType',
|
|
width: 120,
|
|
render: (val, record, index) => (
|
|
<Select
|
|
value={val}
|
|
disabled={record.disabled}
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
options={typeIndexOptions}
|
|
onChange={(value) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].fieldIndexType = value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '字段长度/枚举值',
|
|
dataIndex: 'dataTypeLong',
|
|
width: 140,
|
|
render: (val, record, index) => (
|
|
<Input
|
|
value={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].dataTypeLong = e.target.value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '数据库字段',
|
|
dataIndex: 'columnName',
|
|
width: 140,
|
|
render: (val, record, index) => (
|
|
<Input
|
|
value={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].columnName = e.target.value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '数据库字段描述',
|
|
dataIndex: 'comment',
|
|
width: 140,
|
|
render: (val, record, index) => (
|
|
<Input
|
|
value={val}
|
|
disabled={record.disabled}
|
|
onChange={(e) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].comment = e.target.value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '搜索条件',
|
|
dataIndex: 'fieldSearchType',
|
|
width: 120,
|
|
render: (val, record, index) => (
|
|
<Select
|
|
value={val}
|
|
disabled={record.fieldType === 'json' || record.disabled}
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
onChange={(value) => {
|
|
const newFields = [...form.fields];
|
|
newFields[index].fieldSearchType = value;
|
|
setForm(prev => ({ ...prev, fields: newFields }));
|
|
}}
|
|
>
|
|
{typeSearchOptions.map(opt => (
|
|
<Select.Option
|
|
key={opt.value}
|
|
value={opt.value}
|
|
disabled={canSelect(record.fieldType, opt.value)}
|
|
>
|
|
{opt.label}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
),
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
width: 150,
|
|
fixed: 'right',
|
|
render: (_, record, index) => (
|
|
<Space>
|
|
{!record.disabled && (
|
|
<>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
icon={<EditOutlined />}
|
|
onClick={() => editAndAddField(record)}
|
|
>
|
|
高级编辑
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
danger
|
|
icon={<DeleteOutlined />}
|
|
onClick={() => deleteField(index)}
|
|
>
|
|
删除
|
|
</Button>
|
|
</>
|
|
)}
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
|
|
return (
|
|
<PageContainer>
|
|
{/* 警告栏 */}
|
|
<Alert
|
|
message={
|
|
<span>
|
|
此功能为开发环境使用,不建议发布到生产,
|
|
<a href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3" target="_blank" rel="noreferrer">
|
|
具体使用效果请点我观看
|
|
</a>
|
|
</span>
|
|
}
|
|
type="warning"
|
|
showIcon
|
|
className={styles.warningBar}
|
|
/>
|
|
|
|
{/* AI生成区域 */}
|
|
{!isAdd && (
|
|
<Card
|
|
title={
|
|
<span>
|
|
使用AI创建
|
|
<a
|
|
href="https://go-kratos.dev/"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
style={{ fontSize: 12, marginLeft: 16 }}
|
|
>
|
|
获取AiPath
|
|
</a>
|
|
</span>
|
|
}
|
|
className={styles.sectionCard}
|
|
>
|
|
<div className={styles.aiSection}>
|
|
<TextArea
|
|
value={prompt}
|
|
onChange={(e) => setPrompt(e.target.value)}
|
|
onFocus={handleFocus}
|
|
onBlur={handleBlur}
|
|
rows={5}
|
|
maxLength={2000}
|
|
placeholder={`现已完全免费\n试试复制一张图片然后按下ctrl+v或者commend+v\n试试描述你的表,让AI帮你完成。\n此功能需要到插件市场个人中心获取自己的AI-Path,把AI-Path填入config.yaml下的autocode-->ai-path,重启项目即可使用。\n按下 Ctrl+Enter 或 Cmd+Enter 直接生成`}
|
|
/>
|
|
<div className={styles.aiButtons}>
|
|
<Tooltip title="前往插件市场个人中心申请AIPath">
|
|
<Button
|
|
type="primary"
|
|
icon={<EyeOutlined />}
|
|
disabled={form.onlyTemplate}
|
|
onClick={eyeFunc}
|
|
>
|
|
识图
|
|
</Button>
|
|
</Tooltip>
|
|
<Tooltip title="前往插件市场个人中心申请AIPath">
|
|
<Button
|
|
type="primary"
|
|
icon={<RobotOutlined />}
|
|
disabled={form.onlyTemplate}
|
|
onClick={() => llmAutoFunc()}
|
|
loading={loading}
|
|
>
|
|
生成
|
|
</Button>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
)}
|
|
|
|
{/* 从数据库创建 */}
|
|
{!isAdd && (
|
|
<Card title="从数据库创建" className={styles.sectionCard}>
|
|
<Row gutter={16}>
|
|
<Col span={6}>
|
|
<Form.Item
|
|
label={
|
|
<Tooltip title="需要提前到db-list自行配置多数据库,如未配置需配置后重启服务方可使用">
|
|
业务库 <QuestionCircleOutlined />
|
|
</Tooltip>
|
|
}
|
|
>
|
|
<Select
|
|
value={dbform.businessDB}
|
|
placeholder="选择业务库"
|
|
allowClear
|
|
onChange={(val) => {
|
|
setDbform(prev => ({ ...prev, businessDB: val, dbName: '', tableName: '' }));
|
|
getDbFunc();
|
|
}}
|
|
>
|
|
{dbList.map((db) => (
|
|
<Select.Option key={db.aliasName} value={db.aliasName} disabled={db.disable}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span>{db.aliasName}</span>
|
|
<span style={{ color: '#8492a6', fontSize: 13 }}>{db.dbName}</span>
|
|
</div>
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item label="数据库名">
|
|
<Select
|
|
value={dbform.dbName}
|
|
placeholder="请选择数据库"
|
|
allowClear
|
|
onChange={(val) => {
|
|
setDbform(prev => ({ ...prev, dbName: val, tableName: '' }));
|
|
getTableFunc();
|
|
}}
|
|
>
|
|
{dbOptions.map((db: any) => (
|
|
<Select.Option key={db.database} value={db.database}>
|
|
{db.database}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item label="表名">
|
|
<Select
|
|
value={dbform.tableName}
|
|
placeholder="请选择表"
|
|
disabled={!dbform.dbName}
|
|
allowClear
|
|
showSearch
|
|
onChange={(val) => setDbform(prev => ({ ...prev, tableName: val }))}
|
|
>
|
|
{tableOptions.map((t: any) => (
|
|
<Select.Option key={t.tableName} value={t.tableName}>
|
|
{t.tableName}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item>
|
|
<Button type="primary" onClick={getColumnFunc}>
|
|
使用此表
|
|
</Button>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
</Card>
|
|
)}
|
|
|
|
{/* 自动化结构 */}
|
|
<Card title="自动化结构" className={styles.sectionCard}>
|
|
<Row gutter={16}>
|
|
<Col span={6}>
|
|
<Form.Item label="结构名称" required>
|
|
<Space.Compact style={{ width: '100%' }}>
|
|
<Input
|
|
value={form.structName}
|
|
disabled={isAdd}
|
|
placeholder="首字母自动转换大写"
|
|
onChange={(e) => setForm(prev => ({ ...prev, structName: e.target.value }))}
|
|
/>
|
|
<Button
|
|
type="primary"
|
|
icon={<RobotOutlined />}
|
|
disabled={form.onlyTemplate || isAdd}
|
|
onClick={() => llmAutoFunc(true)}
|
|
>
|
|
生成
|
|
</Button>
|
|
</Space.Compact>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item
|
|
label={
|
|
<Tooltip title="简称会作为入参对象名和路由group">
|
|
结构简称 <QuestionCircleOutlined />
|
|
</Tooltip>
|
|
}
|
|
required
|
|
>
|
|
<Input
|
|
value={form.abbreviation}
|
|
disabled={isAdd}
|
|
placeholder="请输入Struct简称"
|
|
onChange={(e) => setForm(prev => ({ ...prev, abbreviation: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item label="中文名称" required>
|
|
<Input
|
|
value={form.description}
|
|
disabled={isAdd}
|
|
placeholder="中文描述作为自动api描述"
|
|
onChange={(e) => setForm(prev => ({ ...prev, description: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item label="表名">
|
|
<Input
|
|
value={form.tableName}
|
|
disabled={isAdd}
|
|
placeholder="指定表名(非必填)"
|
|
onChange={(e) => setForm(prev => ({ ...prev, tableName: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={6}>
|
|
<Form.Item
|
|
label={
|
|
<Tooltip title="生成文件的默认名称(建议为驼峰格式,首字母小写,如sysXxxXxxx)">
|
|
文件名称 <QuestionCircleOutlined />
|
|
</Tooltip>
|
|
}
|
|
required
|
|
>
|
|
<Input
|
|
value={form.packageName}
|
|
disabled={isAdd}
|
|
placeholder="请输入文件名称"
|
|
onChange={(e) => setForm(prev => ({ ...prev, packageName: e.target.value }))}
|
|
onBlur={() => setForm(prev => ({ ...prev, packageName: toLowerCase(prev.packageName) }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item
|
|
label={
|
|
<Tooltip title="模板package会创建集成于项目本体中的代码包,模板plugin会创建插件包">
|
|
模板类型 <QuestionCircleOutlined />
|
|
</Tooltip>
|
|
}
|
|
required
|
|
>
|
|
<Select
|
|
value={form.templateType}
|
|
disabled={isAdd}
|
|
placeholder="请选择模板类型"
|
|
style={{ width: '100%' }}
|
|
onChange={(val) => setForm(prev => ({ ...prev, templateType: val }))}
|
|
>
|
|
{templateTypes.map((type) => (
|
|
<Select.Option key={type} value={type}>
|
|
{type === 'package' ? 'package (项目代码包)' : 'plugin (插件包)'}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item label="选择模板" required>
|
|
<Space.Compact style={{ width: '100%' }}>
|
|
<Select
|
|
value={form.package}
|
|
disabled={isAdd}
|
|
placeholder="请选择模板"
|
|
showSearch
|
|
style={{ flex: 1 }}
|
|
onChange={(val) => setForm(prev => ({ ...prev, package: val }))}
|
|
>
|
|
{pkgs.map((pkg: any) => (
|
|
<Select.Option key={pkg.ID} value={pkg.packageName}>
|
|
{pkg.packageName}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
<Button icon={<ReloadOutlined />} onClick={getPkgs} />
|
|
<Button icon={<FileAddOutlined />} onClick={goPkgs} />
|
|
</Space.Compact>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={6}>
|
|
<Form.Item
|
|
label={
|
|
<Tooltip title="需要提前到db-list自行配置多数据库,此项为空则会使用kra本库创建自动化代码">
|
|
业务库 <QuestionCircleOutlined />
|
|
</Tooltip>
|
|
}
|
|
>
|
|
<Select
|
|
value={form.businessDB}
|
|
disabled={isAdd}
|
|
placeholder="选择业务库"
|
|
allowClear
|
|
onChange={(val) => setForm(prev => ({ ...prev, businessDB: val }))}
|
|
>
|
|
{dbList.map((db) => (
|
|
<Select.Option key={db.aliasName} value={db.aliasName} disabled={db.disable}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span>{db.aliasName}</span>
|
|
<span style={{ color: '#8492a6', fontSize: 13 }}>{db.dbName}</span>
|
|
</div>
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
</Card>
|
|
|
|
{/* 专家模式 */}
|
|
<Card className={styles.sectionCard}>
|
|
<Collapse ghost className={styles.expertCollapse}>
|
|
<Panel header="专家模式" key="expert">
|
|
<div className={styles.expertSection}>
|
|
{/* 基础设置 */}
|
|
<div className={styles.settingGroup}>
|
|
<h4>基础设置</h4>
|
|
<Row gutter={16}>
|
|
<Col span={4}>
|
|
<Tooltip title="会自动在结构体global.Model其中包含主键和软删除相关操作配置">
|
|
<Checkbox
|
|
checked={form.gvaModel}
|
|
disabled={isAdd}
|
|
onChange={(e) => useKraModel(e.target.checked)}
|
|
>
|
|
使用KRA结构
|
|
</Checkbox>
|
|
</Tooltip>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Tooltip title="会自动产生页面内的按钮权限配置,若不在角色管理中进行按钮分配则按钮不可见">
|
|
<Checkbox
|
|
checked={form.autoCreateBtnAuth}
|
|
disabled={!form.generateWeb || isAdd}
|
|
onChange={(e) => setForm(prev => ({ ...prev, autoCreateBtnAuth: e.target.checked }))}
|
|
>
|
|
创建按钮权限
|
|
</Checkbox>
|
|
</Tooltip>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Checkbox
|
|
checked={form.generateWeb}
|
|
disabled={isAdd}
|
|
onChange={(e) => setForm(prev => ({ ...prev, generateWeb: e.target.checked }))}
|
|
>
|
|
生成前端
|
|
</Checkbox>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Checkbox checked={form.generateServer} disabled>
|
|
生成后端
|
|
</Checkbox>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
|
|
{/* 自动化设置 */}
|
|
<div className={styles.settingGroup}>
|
|
<h4>自动化设置</h4>
|
|
<Row gutter={16}>
|
|
<Col span={4}>
|
|
<Tooltip title="把自动生成的API注册进数据库">
|
|
<Checkbox
|
|
checked={form.autoCreateApiToSql}
|
|
disabled={!form.generateServer || isAdd}
|
|
onChange={(e) => setForm(prev => ({ ...prev, autoCreateApiToSql: e.target.checked }))}
|
|
>
|
|
自动创建API
|
|
</Checkbox>
|
|
</Tooltip>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Tooltip title="把自动生成的菜单注册进数据库">
|
|
<Checkbox
|
|
checked={form.autoCreateMenuToSql}
|
|
disabled={!form.generateWeb || isAdd}
|
|
onChange={(e) => setForm(prev => ({ ...prev, autoCreateMenuToSql: e.target.checked }))}
|
|
>
|
|
自动创建菜单
|
|
</Checkbox>
|
|
</Tooltip>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Tooltip title="自动同步数据库表结构,如果不需要可以选择关闭">
|
|
<Checkbox
|
|
checked={form.autoMigrate}
|
|
disabled={!form.generateServer || isAdd}
|
|
onChange={(e) => setForm(prev => ({ ...prev, autoMigrate: e.target.checked }))}
|
|
>
|
|
同步表结构
|
|
</Checkbox>
|
|
</Tooltip>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
|
|
{/* 高级设置 */}
|
|
<div className={styles.settingGroup}>
|
|
<h4>高级设置</h4>
|
|
<Row gutter={16}>
|
|
<Col span={4}>
|
|
<Tooltip title="会自动在结构体添加 created_by updated_by deleted_by,方便用户进行资源权限控制">
|
|
<Checkbox
|
|
checked={form.autoCreateResource}
|
|
disabled={isAdd}
|
|
onChange={(e) => setForm(prev => ({ ...prev, autoCreateResource: e.target.checked }))}
|
|
>
|
|
创建资源标识
|
|
</Checkbox>
|
|
</Tooltip>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Tooltip title="使用基础模板将不会生成任何结构体和CURD,仅仅配置enter等属性方便自行开发非CURD逻辑">
|
|
<Checkbox
|
|
checked={form.onlyTemplate}
|
|
disabled={isAdd}
|
|
onChange={(e) => {
|
|
if (e.target.checked) {
|
|
Modal.confirm({
|
|
title: '注意',
|
|
content: '使用基础模板将不会生成任何结构体和CURD,仅仅配置enter等属性方便自行开发非CURD逻辑',
|
|
onOk: () => {
|
|
setForm(prev => ({ ...prev, onlyTemplate: true, fields: [] }));
|
|
},
|
|
});
|
|
} else {
|
|
setForm(prev => ({ ...prev, onlyTemplate: false }));
|
|
}
|
|
}}
|
|
>
|
|
基础模板
|
|
</Checkbox>
|
|
</Tooltip>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
|
|
{/* 树形结构设置 */}
|
|
<div className={styles.settingGroup}>
|
|
<h4>树形结构设置</h4>
|
|
<Row gutter={16} align="middle">
|
|
<Col span={4}>
|
|
<Tooltip title="会自动创建parentID来进行父子关系关联,仅支持主键为int类型">
|
|
<Checkbox
|
|
checked={form.isTree}
|
|
disabled={isAdd}
|
|
onChange={(e) => setForm(prev => ({ ...prev, isTree: e.target.checked }))}
|
|
>
|
|
树型结构
|
|
</Checkbox>
|
|
</Tooltip>
|
|
</Col>
|
|
<Col span={8}>
|
|
<Input
|
|
value={form.treeJson}
|
|
disabled={!form.isTree || isAdd}
|
|
placeholder="前端展示json属性"
|
|
onChange={(e) => setForm(prev => ({ ...prev, treeJson: e.target.value }))}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
</div>
|
|
</Panel>
|
|
</Collapse>
|
|
</Card>
|
|
|
|
{/* 字段列表 */}
|
|
<Card className={styles.sectionCard}>
|
|
<div className={styles.toolbar}>
|
|
<Button
|
|
type="primary"
|
|
icon={<PlusOutlined />}
|
|
onClick={() => editAndAddField()}
|
|
disabled={form.onlyTemplate}
|
|
>
|
|
新增字段
|
|
</Button>
|
|
</div>
|
|
<Table
|
|
rowKey="onlyNumber"
|
|
columns={fieldColumns}
|
|
dataSource={form.fields}
|
|
pagination={false}
|
|
scroll={{ x: 2200 }}
|
|
size="small"
|
|
className={styles.fieldTable}
|
|
/>
|
|
<div className={styles.actionBar}>
|
|
<Space>
|
|
<Button icon={<DownloadOutlined />} disabled={isAdd} onClick={exportJson}>
|
|
导出json
|
|
</Button>
|
|
<Upload beforeUpload={importJson} showUploadList={false} accept=".json">
|
|
<Button icon={<UploadOutlined />} disabled={isAdd}>
|
|
导入json
|
|
</Button>
|
|
</Upload>
|
|
<Button icon={<ClearOutlined />} disabled={isAdd} onClick={clearCatch}>
|
|
清除暂存
|
|
</Button>
|
|
<Button icon={<SaveOutlined />} disabled={isAdd} onClick={catchData}>
|
|
暂存
|
|
</Button>
|
|
<Button
|
|
type="primary"
|
|
icon={<CodeOutlined />}
|
|
disabled={isAdd}
|
|
loading={loading}
|
|
onClick={() => enterForm(false)}
|
|
>
|
|
生成代码
|
|
</Button>
|
|
<Button
|
|
type="primary"
|
|
icon={<EyeOutlined />}
|
|
loading={loading}
|
|
onClick={() => enterForm(true)}
|
|
>
|
|
{isAdd ? '查看代码' : '预览代码'}
|
|
</Button>
|
|
</Space>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* 字段编辑抽屉 */}
|
|
<Drawer
|
|
title="组件内容"
|
|
open={fieldDialogVisible}
|
|
onClose={closeDialog}
|
|
width="70%"
|
|
extra={
|
|
<Space>
|
|
<Button onClick={closeDialog}>取消</Button>
|
|
<Button type="primary" onClick={enterDialog}>
|
|
确定
|
|
</Button>
|
|
</Space>
|
|
}
|
|
>
|
|
<Alert
|
|
message="id, created_at, updated_at, deleted_at 会自动生成请勿重复创建。搜索时如果条件为LIKE只支持字符串"
|
|
type="warning"
|
|
showIcon
|
|
style={{ marginBottom: 16 }}
|
|
/>
|
|
<Form layout="vertical">
|
|
<Row gutter={16}>
|
|
<Col span={12}>
|
|
<Form.Item label="字段名称" required>
|
|
<Space.Compact style={{ width: '100%' }}>
|
|
<Input
|
|
value={dialogMiddle.fieldName}
|
|
placeholder="请输入字段名称"
|
|
style={{ width: '80%' }}
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, fieldName: e.target.value }))}
|
|
/>
|
|
<Button style={{ width: '20%' }} onClick={autoFillField}>
|
|
自动填充
|
|
</Button>
|
|
</Space.Compact>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item label="字段中文名" required>
|
|
<Input
|
|
value={dialogMiddle.fieldDesc}
|
|
placeholder="请输入字段中文名"
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, fieldDesc: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={12}>
|
|
<Form.Item label="字段JSON" required>
|
|
<Input
|
|
value={dialogMiddle.fieldJson}
|
|
placeholder="请输入字段JSON"
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, fieldJson: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item label="数据库字段名" required>
|
|
<Input
|
|
value={dialogMiddle.columnName}
|
|
placeholder="请输入数据库字段名"
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, columnName: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={12}>
|
|
<Form.Item label="数据库字段描述">
|
|
<Input
|
|
value={dialogMiddle.comment}
|
|
placeholder="请输入数据库字段描述"
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, comment: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item label="字段类型" required>
|
|
<Select
|
|
value={dialogMiddle.fieldType}
|
|
options={typeOptions}
|
|
placeholder="请选择字段类型"
|
|
style={{ width: '100%' }}
|
|
onChange={(val) => {
|
|
setDialogMiddle(prev => ({ ...prev, fieldType: val }));
|
|
clearFieldTypeOptions();
|
|
}}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={12}>
|
|
<Form.Item label={dialogMiddle.fieldType === 'enum' ? '枚举值' : '类型长度'}>
|
|
<Input
|
|
value={dialogMiddle.dataTypeLong}
|
|
placeholder={dialogMiddle.fieldType === 'enum' ? "例:'北京','天津'" : '数据库类型长度'}
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, dataTypeLong: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item label="字段查询条件">
|
|
<Select
|
|
value={dialogMiddle.fieldSearchType}
|
|
disabled={dialogMiddle.fieldType === 'json'}
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
placeholder="请选择字段查询条件"
|
|
onChange={(val) => setDialogMiddle(prev => ({ ...prev, fieldSearchType: val }))}
|
|
>
|
|
{typeSearchOptions.map(opt => (
|
|
<Select.Option
|
|
key={opt.value}
|
|
value={opt.value}
|
|
disabled={canSelect(dialogMiddle.fieldType, opt.value)}
|
|
>
|
|
{opt.label}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={12}>
|
|
<Form.Item label="关联字典">
|
|
<Select
|
|
value={dialogMiddle.dictType}
|
|
disabled={dialogMiddle.fieldType !== 'string' && dialogMiddle.fieldType !== 'array'}
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
placeholder="请选择字典"
|
|
onChange={(val) => setDialogMiddle(prev => ({ ...prev, dictType: val }))}
|
|
>
|
|
{dictOptions.map((dict: any) => (
|
|
<Select.Option key={dict.type} value={dict.type}>
|
|
{`${dict.type}(${dict.name})`}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item label="默认值">
|
|
<Input
|
|
value={dialogMiddle.defaultValue}
|
|
placeholder="请输入默认值"
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, defaultValue: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={12}>
|
|
<Form.Item label="索引类型">
|
|
<Select
|
|
value={dialogMiddle.fieldIndexType}
|
|
disabled={dialogMiddle.fieldType === 'json'}
|
|
options={typeIndexOptions}
|
|
placeholder="请选择字段索引类型"
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
onChange={(val) => setDialogMiddle(prev => ({ ...prev, fieldIndexType: val }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Form.Item label="校验失败文案">
|
|
<Input
|
|
value={dialogMiddle.errorText}
|
|
placeholder="请输入校验失败时的提示文案"
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, errorText: e.target.value }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={4}>
|
|
<Form.Item label="主键">
|
|
<Checkbox
|
|
checked={dialogMiddle.primaryKey}
|
|
onChange={(e) => setDialogMiddle(prev => ({ ...prev, primaryKey: e.target.checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Form.Item label="前端新建/编辑">
|
|
<Switch
|
|
checked={dialogMiddle.form}
|
|
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, form: checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Form.Item label="前端表格列">
|
|
<Switch
|
|
checked={dialogMiddle.table}
|
|
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, table: checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Form.Item label="前端详情">
|
|
<Switch
|
|
checked={dialogMiddle.desc}
|
|
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, desc: checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Form.Item label="导入/导出">
|
|
<Switch
|
|
checked={dialogMiddle.excel}
|
|
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, excel: checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Form.Item label="是否排序">
|
|
<Switch
|
|
checked={dialogMiddle.sort}
|
|
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, sort: checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={16}>
|
|
<Col span={4}>
|
|
<Form.Item label="是否必填">
|
|
<Switch
|
|
checked={dialogMiddle.require}
|
|
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, require: checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Form.Item label="是否可清空">
|
|
<Switch
|
|
checked={dialogMiddle.clearable}
|
|
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, clearable: checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Form.Item label="隐藏查询条件">
|
|
<Switch
|
|
checked={dialogMiddle.fieldSearchHide}
|
|
disabled={!dialogMiddle.fieldSearchType}
|
|
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, fieldSearchHide: checked }))}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
</Form>
|
|
|
|
{/* 数据源配置 */}
|
|
<Collapse
|
|
activeKey={dataSourceCollapsed}
|
|
onChange={(keys) => setDataSourceCollapsed(keys as string[])}
|
|
style={{ marginTop: 16 }}
|
|
>
|
|
<Panel
|
|
header="数据源配置(此配置为高级配置,如编程基础不牢,可能导致自动化代码不可用)"
|
|
key="dataSource"
|
|
>
|
|
<Row gutter={8}>
|
|
<Col span={4}>
|
|
<Select
|
|
value={dialogMiddle.dataSource?.dbName}
|
|
placeholder="数据库【不填则为KRA库】"
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
onChange={handleDataSourceDbChange}
|
|
>
|
|
{dbList.map((db) => (
|
|
<Select.Option key={db.aliasName} value={db.aliasName} disabled={db.disable}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
<span>{db.aliasName}</span>
|
|
<span style={{ color: '#8492a6', fontSize: 13 }}>{db.dbName}</span>
|
|
</div>
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Col>
|
|
<Col span={4}>
|
|
<Select
|
|
value={dialogMiddle.dataSource?.association}
|
|
placeholder="关联模式"
|
|
style={{ width: '100%' }}
|
|
onChange={handleAssociationChange}
|
|
>
|
|
<Select.Option value={1}>一对一</Select.Option>
|
|
<Select.Option value={2}>一对多</Select.Option>
|
|
</Select>
|
|
</Col>
|
|
<Col span={5}>
|
|
<Select
|
|
value={dialogMiddle.dataSource?.table}
|
|
placeholder="请选择数据源表"
|
|
allowClear
|
|
showSearch
|
|
style={{ width: '100%' }}
|
|
onFocus={() => getDBTableListFunc()}
|
|
onChange={(val) => selectDataSourceTable(val)}
|
|
onClear={clearDataSourceAccess}
|
|
>
|
|
{dbTableList.map((t: any) => (
|
|
<Select.Option key={t.tableName} value={t.tableName}>
|
|
{t.tableName}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Col>
|
|
<Col span={5}>
|
|
<Select
|
|
value={dialogMiddle.dataSource?.value}
|
|
placeholder="请先选择需要存储的数据"
|
|
style={{ width: '100%' }}
|
|
onChange={(val) => setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: { ...prev.dataSource!, value: val },
|
|
}))}
|
|
>
|
|
{dbColumnList.map((col: any) => (
|
|
<Select.Option key={col.columnName} value={col.columnName}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span>
|
|
<Tag color={col.isPrimary ? 'blue' : 'default'} style={{ marginRight: 4 }}>
|
|
{col.isPrimary ? '主键' : '非主键'}
|
|
</Tag>
|
|
{col.columnName}
|
|
</span>
|
|
<span style={{ color: '#8492a6', fontSize: 12 }}>
|
|
{col.type}{col.comment ? ` - ${col.comment}` : ''}
|
|
</span>
|
|
</div>
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Col>
|
|
<Col span={5}>
|
|
<Select
|
|
value={dialogMiddle.dataSource?.label}
|
|
placeholder="请先选择需要展示的数据"
|
|
style={{ width: '100%' }}
|
|
onChange={(val) => setDialogMiddle(prev => ({
|
|
...prev,
|
|
dataSource: { ...prev.dataSource!, label: val },
|
|
}))}
|
|
>
|
|
{dbColumnList.map((col: any) => (
|
|
<Select.Option key={col.columnName} value={col.columnName}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<span>
|
|
<Tag color={col.isPrimary ? 'blue' : 'default'} style={{ marginRight: 4 }}>
|
|
{col.isPrimary ? '主键' : '非主键'}
|
|
</Tag>
|
|
{col.columnName}
|
|
</span>
|
|
<span style={{ color: '#8492a6', fontSize: 12 }}>
|
|
{col.type}{col.comment ? ` - ${col.comment}` : ''}
|
|
</span>
|
|
</div>
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Col>
|
|
</Row>
|
|
</Panel>
|
|
</Collapse>
|
|
</Drawer>
|
|
|
|
{/* 预览抽屉 */}
|
|
<Drawer
|
|
title="代码预览"
|
|
open={previewVisible}
|
|
onClose={() => setPreviewVisible(false)}
|
|
width="85%"
|
|
destroyOnClose
|
|
>
|
|
<CodePreview previewCode={previewCode} isAdd={isAdd} />
|
|
</Drawer>
|
|
</PageContainer>
|
|
);
|
|
};
|
|
|
|
export default AutoCodePage;
|