This commit is contained in:
dengqichen 2025-10-22 09:09:47 +08:00
parent e28e3be451
commit 8a9e221b22
5 changed files with 23 additions and 11097 deletions

10532
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,9 +12,6 @@
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@ant-design/pro-components": "^2.8.2",
"@formily/antd-v5": "^1.2.3",
"@formily/core": "^2.3.2",
"@formily/react": "^2.3.2",
"@hookform/resolvers": "^3.9.1",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.12",
@ -45,7 +42,6 @@
"cmdk": "^1.0.4",
"dagre": "^0.8.5",
"dayjs": "^1.11.13",
"form-render": "^2.5.1",
"less": "^4.2.1",
"monaco-editor": "^0.52.2",
"react": "^18.2.0",

View File

@ -1,420 +0,0 @@
import React, { useState, useEffect } from 'react';
import {Modal, Button, message} from 'antd';
import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons';
import type {DeploymentConfig} from '../types';
import './styles.less';
import {FormButtonGroup, FormItem, Select, Submit, FormGrid, Input, ArrayTable} from '@formily/antd-v5'
import {createForm, Field, FieldDataSource, onFormInit} from '@formily/core'
import {createSchemaField, FormProvider, ISchema} from '@formily/react'
import {action} from '@formily/reactive'
import request from '@/utils/request';
import Editor from '@/components/Editor';
// 定义 ScriptEditor 组件
const ScriptEditor: React.FC<any> = (props) => {
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isFullscreen) {
setIsFullscreen(false);
}
};
document.addEventListener('keydown', handleEscKey);
return () => {
document.removeEventListener('keydown', handleEscKey);
};
}, [isFullscreen]);
const toggleFullscreen = () => {
setIsFullscreen(!isFullscreen);
};
const editorStyle = isFullscreen ? {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 1000,
backgroundColor: '#1e1e1e'
} : {};
return (
<div style={{ position: 'relative', height: isFullscreen ? '100vh' : '240px', ...editorStyle }}>
<Editor {...props} height={isFullscreen ? '100vh' : '240px'} />
<div style={{
position: 'absolute',
top: '10px',
right: '10px',
zIndex: 1001,
display: 'flex',
gap: '8px'
}}>
<Button
type="text"
icon={isFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
onClick={toggleFullscreen}
style={{ color: '#fff' }}
>
{isFullscreen ? '退出全屏' : '全屏'}
</Button>
</div>
</div>
);
};
interface DeploymentConfigModalProps {
open: boolean;
onCancel: () => void;
onSuccess: () => void;
initialValues?: DeploymentConfig;
envId: number;
}
// 定义字段映射接口
interface FieldMapping {
label?: string;
value?: string;
}
const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({
open,
onCancel,
onSuccess,
initialValues,
envId,
}) => {
// 通用的异步数据源加载方法
const useAsyncDataSource = (url: string | null, mapping: FieldMapping = {}) => (field: Field) => {
if (!url) {
field.dataSource = [];
return;
}
const { label = 'name', value = 'id' } = mapping;
field.loading = true;
request.get(url)
.then(action.bound?.((response) => {
field.dataSource = response.map((item: any) => ({
label: item[label],
value: item[value]
}));
field.loading = false;
}))
.catch(action.bound?.((error) => {
console.error(`Failed to load data from ${url}:`, error);
field.dataSource = [];
field.loading = false;
}));
};
const SchemaField = createSchemaField({
components: {
Select,
FormItem,
FormGrid,
Input,
ArrayTable,
ScriptEditor
},
scope: {
useAsyncDataSource
}
})
// 创建表单实例
const form = createForm()
const schema: ISchema = {
type: 'object',
properties: {
jenkinsConfig: {
type: 'void',
'x-component': 'FormGrid',
'x-component-props': {
maxColumns: 3,
minColumns: 3,
columnGap: 24
},
properties: {
externalSystemId: {
type: 'string',
title: 'Jenkins系统',
required: true,
'x-decorator': 'FormItem',
'x-decorator-props': {
labelCol: 24,
wrapperCol: 24,
layout: 'vertical',
colon: false,
labelAlign: 'left'
},
'x-component': 'Select',
'x-component-props': {
style: {
width: '100%'
},
placeholder: '请选择三方系统',
allowClear: true
},
'x-reactions': ["{{useAsyncDataSource('/api/v1/external-system/list?type=JENKINS', { label: 'name' })}}"],
},
viewId: {
type: 'string',
title: 'Jenkins视图',
required: true,
'x-decorator': 'FormItem',
'x-decorator-props': {
labelCol: 24,
wrapperCol: 24,
layout: 'vertical',
colon: false,
labelAlign: 'left'
},
'x-component': 'Select',
'x-component-props': {
style: {
width: '100%'
},
placeholder: '请选择Jenkins视图',
disabled: '{{!$form.values.externalSystemId}}',
allowClear: true
},
'x-reactions': {
dependencies: ['externalSystemId'],
fulfill: {
state: {
value: undefined
},
run: '{{useAsyncDataSource($deps[0] ? `/api/v1/jenkins-view/list?externalSystemId=${$deps[0]}` : null, { label: "viewName" })($self)}}'
}
}
},
jobId: {
type: 'string',
title: 'Jenkins作业',
required: true,
'x-decorator': 'FormItem',
'x-decorator-props': {
labelCol: 24,
wrapperCol: 24,
layout: 'vertical',
colon: false,
labelAlign: 'left'
},
'x-component': 'Select',
'x-component-props': {
style: {
width: '100%'
},
placeholder: '请选择Jenkins作业',
disabled: '{{!$form.values.viewId}}',
allowClear: true
},
'x-reactions': {
dependencies: ['externalSystemId', 'viewId'],
fulfill: {
state: {
value: undefined
},
run: '{{useAsyncDataSource(($deps[0] && $deps[1]) ? `/api/v1/jenkins-job/list?externalSystemId=${$deps[0]}&viewId=${$deps[1]}` : null, { label: "jobName" })($self)}}'
}
}
},
}
},
envConfig: {
type: 'void',
'x-component': 'FormGrid',
'x-component-props': {
maxColumns: 1,
minColumns: 1
},
properties: {
envs: {
type: 'array',
title: '环境变量',
'x-decorator': 'FormItem',
'x-decorator-props': {
labelCol: 24,
wrapperCol: 24,
layout: 'vertical',
colon: false,
labelAlign: 'left'
},
'x-component': 'ArrayTable',
'x-component-props': {
pagination: { pageSize: 10 },
scroll: { x: '100%' },
style: {
minHeight: '160px',
maxHeight: '240px',
overflow: 'auto'
}
},
items: {
type: 'object',
properties: {
key: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: '键', width: '40%' },
properties: {
key: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': [
{
required: true,
message: '请输入环境变量名'
},
{
pattern: '^[a-zA-Z][a-zA-Z0-9_]*$',
message: '只能包含字母、数字和下划线,且必须以字母开头'
}
],
'x-component-props': {
placeholder: '请输入环境变量名',
allowClear: true
}
}
}
},
value: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: '值', width: '40%' },
properties: {
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': {
required: true,
message: '请输入环境变量值'
},
'x-component-props': {
placeholder: '请输入环境变量值',
allowClear: true
}
}
}
},
column3: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': {
title: '操作',
dataIndex: 'operations',
width: '20%',
},
properties: {
remove: {
type: 'void',
'x-component': 'ArrayTable.Remove'
},
moveUp: {
type: 'void',
'x-component': 'ArrayTable.MoveUp'
},
moveDown: {
type: 'void',
'x-component': 'ArrayTable.MoveDown'
}
}
}
}
},
properties: {
add: {
type: 'void',
title: '添加环境变量',
'x-component': 'ArrayTable.Addition'
}
}
},
script: {
type: 'string',
title: '脚本内容',
'x-decorator': 'FormItem',
'x-decorator-props': {
labelCol: 24,
wrapperCol: 24,
layout: 'vertical',
colon: false,
labelAlign: 'left',
style: {
marginBottom: '16px'
}
},
'x-component': 'ScriptEditor',
'x-component-props': {
language: 'shell',
theme: 'vs-dark',
options: {
minimap: {
enabled: true,
scale: 2,
showSlider: "mouseover",
renderCharacters: false
},
scrollBeyondLastLine: false,
fontSize: 14,
lineNumbers: 'on',
automaticLayout: true,
tabSize: 2
}
}
}
}
}
}
};
const handleSubmit = async () => {
try {
const values = await form.submit()
console.log('表单提交的值:', values)
console.log('Schema:', schema)
console.log('FORMILY', JSON.stringify(schema, null, 2))
onSuccess?.()
form.reset()
} catch (e) {
console.error('表单提交出错:', e)
message.error('提交失败,请检查表单数据是否正确')
}
}
return (
<Modal
title="部署配置"
open={open}
onCancel={onCancel}
width={800}
centered
footer={[
<Button key="cancel" onClick={onCancel}>
</Button>,
<Button key="submit" type="primary" onClick={handleSubmit}>
</Button>
]}
bodyStyle={{
padding: '24px',
maxHeight: '80vh',
overflow: 'auto'
}}
>
<FormProvider form={form}>
<SchemaField schema={schema}/>
</FormProvider>
</Modal>
);
};
export default DeploymentConfigModal;

View File

@ -1,137 +0,0 @@
import React from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription
} from "@/components/ui/dialog";
import type {DeploymentConfig} from '../types';
import {FormButtonGroup, FormItem, Select, Submit} from '@formily/antd-v5'
import {createForm, Field, FieldDataSource} from '@formily/core'
import {createSchemaField, FormProvider, ISchema} from '@formily/react'
import {action} from '@formily/reactive'
interface DeploymentConfigModalProps {
open: boolean;
onCancel: () => void;
onSuccess: () => void;
initialValues?: DeploymentConfig;
envId: number;
}
const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({
open,
onCancel,
onSuccess,
initialValues,
envId,
}) => {
const SchemaField = createSchemaField({
components: {
Select,
FormItem,
},
})
const loadData = async (field: Field) => {
const linkage = field.query('linkage').get('value')
if (!linkage) return []
return new Promise<FieldDataSource>((resolve) => {
setTimeout(() => {
if (linkage === 1) {
resolve([
{
label: 'AAA',
value: 'aaa',
},
{
label: 'BBB',
value: 'ccc',
},
])
} else if (linkage === 2) {
resolve([
{
label: 'CCC',
value: 'ccc',
},
{
label: 'DDD',
value: 'ddd',
},
])
}
}, 1500)
})
}
const useAsyncDataSource = (service: typeof loadData) => (field: Field) => {
field.loading = true
service(field).then(
action.bound?.((data) => {
field.dataSource = data
field.loading = false
})
)
}
const form = createForm()
const schema: ISchema = {
type: 'object',
properties: {
linkage: {
type: 'string',
title: '联动选择框',
enum: [
{label: '发请求1', value: 1},
{label: '发请求2', value: 2},
],
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
style: {
width: 120,
},
},
},
select: {
type: 'string',
title: '异步选择框',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
style: {
width: 120,
},
},
'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
},
},
}
return (
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
Jenkins系统相关参数
</DialogDescription>
</DialogHeader>
<div className="h-[calc(100vh-250px)] overflow-y-auto p-6">
<FormProvider form={form}>
<SchemaField schema={schema} scope={{useAsyncDataSource, loadData}}/>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
</div>
</DialogContent>
</Dialog>
);
};
export default DeploymentConfigModal;

View File

@ -43,11 +43,30 @@ export const getNodeCategory = (nodeType: NodeType | string): NodeCategory => {
// JSON Schema 定义
/**
* JSON Schema - 使 Formily ISchema
* @formily/react @formily/json-schema
* JSON Schema - JSON Schema Draft 7
*
*/
import type { ISchema } from '@formily/react';
export type JSONSchema = ISchema;
export interface JSONSchema {
type?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array';
title?: string;
description?: string;
properties?: Record<string, JSONSchema>;
required?: string[];
default?: any;
enum?: any[];
format?: string;
pattern?: string;
minLength?: number;
maxLength?: number;
minimum?: number;
maximum?: number;
minItems?: number;
maxItems?: number;
'x-component'?: string;
'x-component-props'?: Record<string, any>;
'x-dataSource'?: string;
[key: string]: any; // 允许扩展属性
}
// ========== 输出字段定义 ==========