后端重构
This commit is contained in:
@@ -45,7 +45,10 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"highlight.js": "^11.11.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^17.0.1",
|
||||
"marked-highlight": "^2.2.3",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"qs": "^6.13.0",
|
||||
@@ -67,6 +70,7 @@
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/lodash": "^4.17.13",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@types/node": "^24.0.13",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/qs": "^6.9.17",
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* KRA - Code Preview Component Styles
|
||||
* 代码预览组件样式
|
||||
*/
|
||||
|
||||
.codePreview {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.fileCount {
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.categories {
|
||||
padding: 8px 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
flex: 1;
|
||||
height: calc(100vh - 280px);
|
||||
min-height: 400px;
|
||||
|
||||
:global {
|
||||
.ant-tabs-nav {
|
||||
width: 220px;
|
||||
max-height: calc(100vh - 280px);
|
||||
overflow-y: auto;
|
||||
background: #fafafa;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
|
||||
.ant-tabs-tab {
|
||||
padding: 8px 12px !important;
|
||||
margin: 0 !important;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
&.ant-tabs-tab-active {
|
||||
background: #e6f7ff;
|
||||
border-right: 2px solid #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-content-holder {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ant-tabs-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.filename {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.codeContainer {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.codeHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: #252526;
|
||||
border-bottom: 1px solid #3c3c3c;
|
||||
}
|
||||
|
||||
.filepath {
|
||||
color: #cccccc;
|
||||
font-size: 12px;
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.codeContent {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
|
||||
:global {
|
||||
pre {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent !important;
|
||||
|
||||
code {
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
// highlight.js 样式覆盖
|
||||
.hljs {
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 48px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
// 滚动条样式
|
||||
.codeContent::-webkit-scrollbar,
|
||||
.tabs :global(.ant-tabs-nav)::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.codeContent::-webkit-scrollbar-track,
|
||||
.tabs :global(.ant-tabs-nav)::-webkit-scrollbar-track {
|
||||
background: #2d2d2d;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.codeContent::-webkit-scrollbar-thumb,
|
||||
.tabs :global(.ant-tabs-nav)::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #666;
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色模式支持
|
||||
:global(.ant-layout-content[data-theme='dark']) {
|
||||
.toolbar {
|
||||
background: #1f1f1f;
|
||||
border-color: #303030;
|
||||
}
|
||||
|
||||
.categories {
|
||||
background: #141414;
|
||||
border-color: #303030;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
:global {
|
||||
.ant-tabs-nav {
|
||||
background: #1f1f1f;
|
||||
border-color: #303030;
|
||||
|
||||
.ant-tabs-tab {
|
||||
border-color: #303030;
|
||||
|
||||
&:hover {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
&.ant-tabs-tab-active {
|
||||
background: #111d2c;
|
||||
border-right-color: #177ddc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fileCount {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* KRA - Code Preview Component
|
||||
* 代码预览组件 - 支持语法高亮和文件分类展示
|
||||
*/
|
||||
import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react';
|
||||
import { Tabs, Button, Space, message, Empty, Tooltip, Input, Tag } from 'antd';
|
||||
import {
|
||||
CopyOutlined,
|
||||
SelectOutlined,
|
||||
SearchOutlined,
|
||||
FileTextOutlined,
|
||||
CodeOutlined,
|
||||
Html5Outlined,
|
||||
FileOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Marked } from 'marked';
|
||||
import { markedHighlight } from 'marked-highlight';
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/atom-one-dark.css';
|
||||
import styles from './CodePreview.less';
|
||||
|
||||
interface CodePreviewProps {
|
||||
previewCode: Record<string, string> | null;
|
||||
isAdd?: boolean;
|
||||
}
|
||||
|
||||
// 需要在追加模式下过滤的文件
|
||||
const createKeyFilters = [
|
||||
'enter.go',
|
||||
'gorm_biz.go',
|
||||
'router_biz.go',
|
||||
'api',
|
||||
'router',
|
||||
'initialize',
|
||||
'gen.go',
|
||||
];
|
||||
|
||||
// 文件类型图标映射
|
||||
const getFileIcon = (filename: string) => {
|
||||
const ext = filename.split('.').pop()?.toLowerCase();
|
||||
switch (ext) {
|
||||
case 'go':
|
||||
return <CodeOutlined style={{ color: '#00ADD8' }} />;
|
||||
case 'tsx':
|
||||
case 'ts':
|
||||
return <CodeOutlined style={{ color: '#3178C6' }} />;
|
||||
case 'jsx':
|
||||
case 'js':
|
||||
return <CodeOutlined style={{ color: '#F7DF1E' }} />;
|
||||
case 'vue':
|
||||
return <Html5Outlined style={{ color: '#42B883' }} />;
|
||||
case 'less':
|
||||
case 'css':
|
||||
case 'scss':
|
||||
return <FileTextOutlined style={{ color: '#CC6699' }} />;
|
||||
default:
|
||||
return <FileOutlined />;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取文件类型标签
|
||||
const getFileTypeTag = (filename: string) => {
|
||||
const ext = filename.split('.').pop()?.toLowerCase();
|
||||
const typeMap: Record<string, { color: string; label: string }> = {
|
||||
go: { color: 'cyan', label: 'Go' },
|
||||
tsx: { color: 'blue', label: 'TSX' },
|
||||
ts: { color: 'blue', label: 'TS' },
|
||||
jsx: { color: 'gold', label: 'JSX' },
|
||||
js: { color: 'gold', label: 'JS' },
|
||||
vue: { color: 'green', label: 'Vue' },
|
||||
less: { color: 'magenta', label: 'Less' },
|
||||
css: { color: 'magenta', label: 'CSS' },
|
||||
scss: { color: 'magenta', label: 'SCSS' },
|
||||
};
|
||||
const type = typeMap[ext || ''] || { color: 'default', label: ext?.toUpperCase() || 'FILE' };
|
||||
return <Tag color={type.color}>{type.label}</Tag>;
|
||||
};
|
||||
|
||||
// 获取语言类型
|
||||
const getLanguage = (filename: string): string => {
|
||||
const ext = filename.split('.').pop()?.toLowerCase();
|
||||
const langMap: Record<string, string> = {
|
||||
go: 'go',
|
||||
tsx: 'typescript',
|
||||
ts: 'typescript',
|
||||
jsx: 'javascript',
|
||||
js: 'javascript',
|
||||
vue: 'html',
|
||||
less: 'less',
|
||||
css: 'css',
|
||||
scss: 'scss',
|
||||
json: 'json',
|
||||
yaml: 'yaml',
|
||||
yml: 'yaml',
|
||||
md: 'markdown',
|
||||
sql: 'sql',
|
||||
};
|
||||
return langMap[ext || ''] || 'plaintext';
|
||||
};
|
||||
|
||||
// 文件分类
|
||||
const categorizeFiles = (files: Record<string, string>) => {
|
||||
const categories: Record<string, string[]> = {
|
||||
'Biz 层': [],
|
||||
'Data 层': [],
|
||||
'Service 层': [],
|
||||
'Handler 层': [],
|
||||
'Router 层': [],
|
||||
'前端页面': [],
|
||||
'前端服务': [],
|
||||
'前端类型': [],
|
||||
'其他': [],
|
||||
};
|
||||
|
||||
Object.keys(files).forEach((filename) => {
|
||||
if (filename.includes('/biz/') || filename.includes('\\biz\\')) {
|
||||
categories['Biz 层'].push(filename);
|
||||
} else if (filename.includes('/data/') || filename.includes('\\data\\')) {
|
||||
categories['Data 层'].push(filename);
|
||||
} else if (filename.includes('/service/') || filename.includes('\\service\\')) {
|
||||
categories['Service 层'].push(filename);
|
||||
} else if (filename.includes('/handler/') || filename.includes('\\handler\\')) {
|
||||
categories['Handler 层'].push(filename);
|
||||
} else if (filename.includes('/router/') || filename.includes('\\router\\')) {
|
||||
categories['Router 层'].push(filename);
|
||||
} else if (filename.includes('/pages/') || filename.includes('\\pages\\')) {
|
||||
categories['前端页面'].push(filename);
|
||||
} else if (filename.includes('/services/') || filename.includes('\\services\\')) {
|
||||
categories['前端服务'].push(filename);
|
||||
} else if (filename.includes('/types/') || filename.includes('\\types\\')) {
|
||||
categories['前端类型'].push(filename);
|
||||
} else {
|
||||
categories['其他'].push(filename);
|
||||
}
|
||||
});
|
||||
|
||||
// 过滤空分类
|
||||
return Object.fromEntries(
|
||||
Object.entries(categories).filter(([_, files]) => files.length > 0)
|
||||
);
|
||||
};
|
||||
|
||||
const CodePreview: React.FC<CodePreviewProps> = ({ previewCode, isAdd = false }) => {
|
||||
const [activeKey, setActiveKey] = useState<string>('');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const contentRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
||||
const markedRef = useRef<Marked | null>(null);
|
||||
|
||||
// 过滤后的代码
|
||||
const filteredCode = useMemo(() => {
|
||||
if (!previewCode) return {};
|
||||
|
||||
const result: Record<string, string> = {};
|
||||
for (const key in previewCode) {
|
||||
// 追加模式下过滤特定文件
|
||||
if (isAdd && createKeyFilters.some((filter) => key.includes(filter))) {
|
||||
continue;
|
||||
}
|
||||
// 搜索过滤
|
||||
if (searchText && !key.toLowerCase().includes(searchText.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
result[key] = previewCode[key];
|
||||
}
|
||||
return result;
|
||||
}, [previewCode, isAdd, searchText]);
|
||||
|
||||
// 文件分类
|
||||
const categorizedFiles = useMemo(() => categorizeFiles(filteredCode), [filteredCode]);
|
||||
|
||||
// 初始化 marked
|
||||
useEffect(() => {
|
||||
markedRef.current = new Marked(
|
||||
markedHighlight({
|
||||
langPrefix: 'hljs language-',
|
||||
highlight(code, lang) {
|
||||
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
||||
return hljs.highlight(code, { language }).value;
|
||||
},
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
// 渲染代码
|
||||
useEffect(() => {
|
||||
if (!markedRef.current) return;
|
||||
|
||||
const keys = Object.keys(filteredCode);
|
||||
if (keys.length > 0 && !activeKey) {
|
||||
setActiveKey(keys[0]);
|
||||
}
|
||||
|
||||
// 渲染所有代码块
|
||||
keys.forEach((key) => {
|
||||
const element = contentRefs.current[key];
|
||||
if (element && filteredCode[key]) {
|
||||
const lang = getLanguage(key);
|
||||
const codeBlock = '```' + lang + '\n' + filteredCode[key] + '\n```';
|
||||
element.innerHTML = markedRef.current!.parse(codeBlock) as string;
|
||||
}
|
||||
});
|
||||
}, [filteredCode, activeKey]);
|
||||
|
||||
// 选中当前代码
|
||||
const selectText = useCallback(() => {
|
||||
const element = contentRefs.current[activeKey];
|
||||
if (!element) return;
|
||||
|
||||
if (window.getSelection) {
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}
|
||||
}, [activeKey]);
|
||||
|
||||
// 复制当前代码
|
||||
const copyCode = useCallback(() => {
|
||||
const code = filteredCode[activeKey];
|
||||
if (!code) {
|
||||
message.warning('没有可复制的代码');
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(code).then(
|
||||
() => message.success('复制成功'),
|
||||
() => message.error('复制失败')
|
||||
);
|
||||
}, [activeKey, filteredCode]);
|
||||
|
||||
// 复制所有代码
|
||||
const copyAllCode = useCallback(() => {
|
||||
const allCode = Object.entries(filteredCode)
|
||||
.map(([filename, code]) => `// ===== ${filename} =====\n${code}`)
|
||||
.join('\n\n');
|
||||
|
||||
navigator.clipboard.writeText(allCode).then(
|
||||
() => message.success('复制所有代码成功'),
|
||||
() => message.error('复制失败')
|
||||
);
|
||||
}, [filteredCode]);
|
||||
|
||||
if (!previewCode || Object.keys(filteredCode).length === 0) {
|
||||
return (
|
||||
<Empty
|
||||
description={searchText ? '没有匹配的文件' : '暂无预览代码'}
|
||||
className={styles.empty}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// 构建标签页项
|
||||
const tabItems = Object.keys(filteredCode).map((filename) => ({
|
||||
key: filename,
|
||||
label: (
|
||||
<Tooltip title={filename}>
|
||||
<span className={styles.tabLabel}>
|
||||
{getFileIcon(filename)}
|
||||
<span className={styles.filename}>{filename.split(/[/\\]/).pop()}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
children: (
|
||||
<div className={styles.codeContainer}>
|
||||
<div className={styles.codeHeader}>
|
||||
<span className={styles.filepath}>
|
||||
{getFileTypeTag(filename)}
|
||||
{filename}
|
||||
</span>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<SelectOutlined />}
|
||||
onClick={selectText}
|
||||
>
|
||||
全选
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={copyCode}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<div
|
||||
ref={(el) => (contentRefs.current[filename] = el)}
|
||||
className={styles.codeContent}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={styles.codePreview}>
|
||||
<div className={styles.toolbar}>
|
||||
<Input
|
||||
placeholder="搜索文件..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
<Space>
|
||||
<span className={styles.fileCount}>
|
||||
共 {Object.keys(filteredCode).length} 个文件
|
||||
</span>
|
||||
<Button icon={<CopyOutlined />} onClick={copyAllCode}>
|
||||
复制全部
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* 文件分类统计 */}
|
||||
<div className={styles.categories}>
|
||||
{Object.entries(categorizedFiles).map(([category, files]) => (
|
||||
<Tag key={category} color="processing">
|
||||
{category}: {files.length}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
activeKey={activeKey}
|
||||
onChange={setActiveKey}
|
||||
tabPosition="left"
|
||||
items={tabItems}
|
||||
className={styles.tabs}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodePreview;
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* KRA - AutoCode Components
|
||||
* 自动代码生成页面组件导出
|
||||
*/
|
||||
|
||||
export { default as CodePreview } from './CodePreview';
|
||||
@@ -66,6 +66,44 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.fieldTable {
|
||||
:global {
|
||||
.ant-table-row {
|
||||
&:hover {
|
||||
.dragHandle {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dragHandle {
|
||||
cursor: move;
|
||||
color: #999;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #096dd9;
|
||||
}
|
||||
}
|
||||
|
||||
.expertCollapse {
|
||||
:global {
|
||||
.ant-collapse-header {
|
||||
padding: 12px 0 !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode support
|
||||
:global(.ant-layout-content[data-theme='dark']) {
|
||||
.settingGroup {
|
||||
@@ -75,4 +113,12 @@
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
.dragHandle {
|
||||
color: #666;
|
||||
|
||||
&:hover {
|
||||
color: #177ddc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
Alert,
|
||||
Upload,
|
||||
Modal,
|
||||
Switch,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import {
|
||||
@@ -38,6 +40,7 @@ import {
|
||||
HolderOutlined,
|
||||
ReloadOutlined,
|
||||
FileAddOutlined,
|
||||
MenuOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useParams, useSearchParams, useNavigate } from '@umijs/max';
|
||||
@@ -51,10 +54,13 @@ import {
|
||||
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;
|
||||
@@ -81,6 +87,7 @@ const fieldTemplate = {
|
||||
primaryKey: false,
|
||||
clearable: true,
|
||||
fieldSearchType: '',
|
||||
fieldSearchHide: false,
|
||||
fieldIndexType: '',
|
||||
dictType: '',
|
||||
onlyNumber: '',
|
||||
@@ -181,11 +188,19 @@ interface FieldItem {
|
||||
primaryKey: boolean;
|
||||
clearable?: boolean;
|
||||
fieldSearchType: string;
|
||||
fieldSearchHide?: boolean;
|
||||
fieldIndexType: string;
|
||||
dictType?: string;
|
||||
onlyNumber?: string;
|
||||
disabled?: boolean;
|
||||
dataSource?: any;
|
||||
dataSource?: {
|
||||
dbName: string;
|
||||
association: number;
|
||||
table: string;
|
||||
label: string;
|
||||
value: string;
|
||||
hasDeletedAt: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
@@ -208,6 +223,7 @@ interface FormData {
|
||||
generateServer: boolean;
|
||||
treeJson: string;
|
||||
humpPackageName?: string;
|
||||
templateType: string; // 模板类型:package 或 plugin
|
||||
fields: FieldItem[];
|
||||
}
|
||||
|
||||
@@ -233,6 +249,9 @@ const AutoCodePage: React.FC = () => {
|
||||
// 模板列表
|
||||
const [pkgs, setPkgs] = useState<any[]>([]);
|
||||
|
||||
// 模板类型选项
|
||||
const [templateTypes, setTemplateTypes] = useState<string[]>(['package', 'plugin']);
|
||||
|
||||
// 字段类型映射
|
||||
const [fdMap, setFdMap] = useState<Record<string, string>>({});
|
||||
|
||||
@@ -256,6 +275,7 @@ const AutoCodePage: React.FC = () => {
|
||||
generateWeb: true,
|
||||
generateServer: true,
|
||||
treeJson: '',
|
||||
templateType: 'package', // 默认使用 package 模板
|
||||
fields: [],
|
||||
});
|
||||
|
||||
@@ -273,6 +293,14 @@ const AutoCodePage: React.FC = () => {
|
||||
// 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') {
|
||||
@@ -361,6 +389,8 @@ const AutoCodePage: React.FC = () => {
|
||||
await getDbFunc();
|
||||
await setFdMapFunc();
|
||||
await getPkgs();
|
||||
await getDictOptionsFunc();
|
||||
await getTemplateTypesFunc();
|
||||
|
||||
// 从路由获取ID
|
||||
const id = params.id;
|
||||
@@ -372,6 +402,30 @@ const AutoCodePage: React.FC = () => {
|
||||
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 {
|
||||
@@ -644,6 +698,176 @@ const AutoCodePage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 自动填充字段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) => {
|
||||
@@ -662,6 +886,11 @@ const AutoCodePage: React.FC = () => {
|
||||
}
|
||||
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 = {
|
||||
@@ -669,6 +898,8 @@ const AutoCodePage: React.FC = () => {
|
||||
onlyNumber: getOnlyNumber(),
|
||||
} as FieldItem;
|
||||
setDialogMiddle(newField);
|
||||
setDbTableList([]);
|
||||
setDbColumnList([]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -767,6 +998,7 @@ const AutoCodePage: React.FC = () => {
|
||||
generateWeb: true,
|
||||
generateServer: true,
|
||||
treeJson: '',
|
||||
templateType: 'package',
|
||||
fields: [],
|
||||
});
|
||||
sessionStorage.removeItem('autoCode');
|
||||
@@ -950,7 +1182,14 @@ const AutoCodePage: React.FC = () => {
|
||||
title: '',
|
||||
key: 'drag',
|
||||
width: 50,
|
||||
render: () => <HolderOutlined className={styles.dragHandle} />,
|
||||
render: (_, __, index) => (
|
||||
<Tooltip title="拖拽排序">
|
||||
<MenuOutlined
|
||||
className={styles.dragHandle}
|
||||
style={{ cursor: 'move', color: '#999' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{ title: '序列', key: 'index', width: 60, render: (_, __, index) => index + 1 },
|
||||
{
|
||||
@@ -1131,11 +1370,39 @@ const AutoCodePage: React.FC = () => {
|
||||
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',
|
||||
@@ -1497,6 +1764,30 @@ const AutoCodePage: React.FC = () => {
|
||||
/>
|
||||
</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%' }}>
|
||||
@@ -1519,6 +1810,8 @@ const AutoCodePage: React.FC = () => {
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
label={
|
||||
@@ -1780,22 +2073,34 @@ const AutoCodePage: React.FC = () => {
|
||||
</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>
|
||||
<Input
|
||||
value={dialogMiddle.fieldName}
|
||||
placeholder="请输入字段名称"
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, fieldName: e.target.value }))}
|
||||
/>
|
||||
<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="中文名">
|
||||
<Form.Item label="字段中文名" required>
|
||||
<Input
|
||||
value={dialogMiddle.fieldDesc}
|
||||
placeholder="请输入中文名"
|
||||
placeholder="请输入字段中文名"
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, fieldDesc: e.target.value }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -1803,35 +2108,25 @@ const AutoCodePage: React.FC = () => {
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item label="字段类型">
|
||||
<Select
|
||||
value={dialogMiddle.fieldType}
|
||||
options={typeOptions}
|
||||
placeholder="请选择字段类型"
|
||||
onChange={(val) => setDialogMiddle(prev => ({ ...prev, fieldType: val }))}
|
||||
<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="字段Json">
|
||||
<Form.Item label="数据库字段名" required>
|
||||
<Input
|
||||
value={dialogMiddle.fieldJson}
|
||||
placeholder="请输入字段Json"
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, fieldJson: e.target.value }))}
|
||||
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.columnName}
|
||||
placeholder="请输入数据库字段"
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, columnName: e.target.value }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="数据库字段描述">
|
||||
<Input
|
||||
@@ -1841,17 +2136,73 @@ const AutoCodePage: React.FC = () => {
|
||||
/>
|
||||
</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="字段长度/枚举值">
|
||||
<Form.Item label={dialogMiddle.fieldType === 'enum' ? '枚举值' : '类型长度'}>
|
||||
<Input
|
||||
value={dialogMiddle.dataTypeLong}
|
||||
placeholder="请输入字段长度或枚举值"
|
||||
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
|
||||
@@ -1864,91 +2215,222 @@ const AutoCodePage: React.FC = () => {
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item label="搜索条件">
|
||||
<Form.Item label="索引类型">
|
||||
<Select
|
||||
value={dialogMiddle.fieldSearchType}
|
||||
options={typeSearchOptions}
|
||||
placeholder="请选择搜索条件"
|
||||
value={dialogMiddle.fieldIndexType}
|
||||
disabled={dialogMiddle.fieldType === 'json'}
|
||||
options={typeIndexOptions}
|
||||
placeholder="请选择字段索引类型"
|
||||
allowClear
|
||||
onChange={(val) => setDialogMiddle(prev => ({ ...prev, fieldSearchType: val }))}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(val) => setDialogMiddle(prev => ({ ...prev, fieldIndexType: val }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="索引类型">
|
||||
<Select
|
||||
value={dialogMiddle.fieldIndexType}
|
||||
options={typeIndexOptions}
|
||||
placeholder="请选择索引类型"
|
||||
allowClear
|
||||
onChange={(val) => setDialogMiddle(prev => ({ ...prev, fieldIndexType: val }))}
|
||||
<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>
|
||||
<Form.Item label="主键">
|
||||
<Checkbox
|
||||
checked={dialogMiddle.primaryKey}
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, primaryKey: e.target.checked }))}
|
||||
>
|
||||
主键
|
||||
</Checkbox>
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item>
|
||||
<Checkbox
|
||||
checked={dialogMiddle.require}
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, require: e.target.checked }))}
|
||||
>
|
||||
必填
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item>
|
||||
<Checkbox
|
||||
checked={dialogMiddle.sort}
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, sort: e.target.checked }))}
|
||||
>
|
||||
排序
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item>
|
||||
<Checkbox
|
||||
<Form.Item label="前端新建/编辑">
|
||||
<Switch
|
||||
checked={dialogMiddle.form}
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, form: e.target.checked }))}
|
||||
>
|
||||
新建/编辑
|
||||
</Checkbox>
|
||||
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, form: checked }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item>
|
||||
<Checkbox
|
||||
<Form.Item label="前端表格列">
|
||||
<Switch
|
||||
checked={dialogMiddle.table}
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, table: e.target.checked }))}
|
||||
>
|
||||
表格
|
||||
</Checkbox>
|
||||
onChange={(checked) => setDialogMiddle(prev => ({ ...prev, table: checked }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item>
|
||||
<Checkbox
|
||||
<Form.Item label="前端详情">
|
||||
<Switch
|
||||
checked={dialogMiddle.desc}
|
||||
onChange={(e) => setDialogMiddle(prev => ({ ...prev, desc: e.target.checked }))}
|
||||
>
|
||||
详情
|
||||
</Checkbox>
|
||||
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>
|
||||
|
||||
{/* 预览抽屉 */}
|
||||
@@ -1956,39 +2438,10 @@ const AutoCodePage: React.FC = () => {
|
||||
title="代码预览"
|
||||
open={previewVisible}
|
||||
onClose={() => setPreviewVisible(false)}
|
||||
width="80%"
|
||||
extra={
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
const pre = document.querySelector('.code-preview');
|
||||
if (pre) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(pre);
|
||||
const selection = window.getSelection();
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}
|
||||
}}
|
||||
>
|
||||
全选
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(JSON.stringify(previewCode, null, 2));
|
||||
message.success('复制成功');
|
||||
}}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
width="85%"
|
||||
destroyOnClose
|
||||
>
|
||||
<pre className={`${styles.codePreview} code-preview`}>
|
||||
{JSON.stringify(previewCode, null, 2)}
|
||||
</pre>
|
||||
<CodePreview previewCode={previewCode} isAdd={isAdd} />
|
||||
</Drawer>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
@@ -80,7 +80,7 @@ const Picture: React.FC = () => {
|
||||
<PageContainer>
|
||||
<WarningBar
|
||||
title="此功能只针对授权用户开放"
|
||||
href="https://www.gin-vue-admin.com/empower/"
|
||||
href="https://github.com/flipped-aurora/gin-vue-admin"
|
||||
/>
|
||||
|
||||
<Card title="AI前端工程师" style={{ marginBottom: 16 }}>
|
||||
|
||||
@@ -134,8 +134,8 @@ const AutoPkg: React.FC = () => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<WarningBar
|
||||
title="模板配置"
|
||||
href="https://www.gin-vue-admin.com/docs/auto_code"
|
||||
title="模板package会创建集成于项目本体中的代码包,模板plugin会创建插件包"
|
||||
href="https://github.com/flipped-aurora/gin-vue-admin"
|
||||
/>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={openDrawer}>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Vendored
+23
-38
@@ -117,44 +117,29 @@ export interface ExportTemplate {
|
||||
conditions: string;
|
||||
}
|
||||
|
||||
// Auto code related
|
||||
export interface AutoCodeField {
|
||||
fieldName: string;
|
||||
fieldDesc: string;
|
||||
fieldType: string;
|
||||
fieldJson: string;
|
||||
dataTypeLong: string;
|
||||
comment: string;
|
||||
columnName: string;
|
||||
fieldSearchType: string;
|
||||
dictType: string;
|
||||
require: boolean;
|
||||
errorText: string;
|
||||
clearable: boolean;
|
||||
sort: boolean;
|
||||
primaryKey: boolean;
|
||||
dataSource: any;
|
||||
checkDataSource: boolean;
|
||||
}
|
||||
|
||||
export interface AutoCodeParams {
|
||||
structName: string;
|
||||
tableName: string;
|
||||
packageName: string;
|
||||
humpPackageName: string;
|
||||
abbreviation: string;
|
||||
description: string;
|
||||
autoCreateApiToSql: boolean;
|
||||
autoCreateMenuToSql: boolean;
|
||||
autoCreateResource: boolean;
|
||||
autoMoveFile: boolean;
|
||||
businessDB: string;
|
||||
fields: AutoCodeField[];
|
||||
primaryField: AutoCodeField;
|
||||
dataSourceMap: Record<string, any>;
|
||||
hasTimer: boolean;
|
||||
hasSearchTimer: boolean;
|
||||
}
|
||||
// Auto code related - 详细类型定义请参考 @/services/kratos/autoCode.ts
|
||||
export type {
|
||||
AutoCodeField,
|
||||
AutoCodeParams,
|
||||
DataSource,
|
||||
CustomMethod,
|
||||
CustomMethodParam,
|
||||
CustomCondition,
|
||||
FieldSearchType,
|
||||
FieldIndexType,
|
||||
DatabaseInfo,
|
||||
TableInfo,
|
||||
ColumnInfo,
|
||||
AutoCodeHistory,
|
||||
AutoCodePackage,
|
||||
AddFuncParams,
|
||||
LLMAutoParams,
|
||||
EyeParams,
|
||||
MCPParams,
|
||||
MCPTool,
|
||||
RollbackParams,
|
||||
PreviewResult,
|
||||
} from '@/services/kratos/autoCode';
|
||||
|
||||
// System error related
|
||||
export interface SysError {
|
||||
|
||||
Reference in New Issue
Block a user