表单设计器

This commit is contained in:
dengqichen 2025-10-23 23:00:14 +08:00
parent c033c10fb9
commit da38d2386b
17 changed files with 327 additions and 187 deletions

View File

@ -0,0 +1,53 @@
/**
*
*
*/
import { DataSourceType, type DataSourceRegistry } from './types';
import { jenkinsServersConfig } from './presets/jenkins';
import { k8sClustersConfig } from './presets/k8s';
import { gitRepositoriesConfig } from './presets/git';
import { dockerRegistriesConfig } from './presets/docker';
import { notificationChannelTypesConfig, notificationChannelsConfig } from './presets/notification';
import { usersConfig, rolesConfig, departmentsConfig } from './presets/user';
/**
*
*/
export const DATA_SOURCE_REGISTRY: DataSourceRegistry = {
[DataSourceType.JENKINS_SERVERS]: jenkinsServersConfig,
[DataSourceType.K8S_CLUSTERS]: k8sClustersConfig,
[DataSourceType.GIT_REPOSITORIES]: gitRepositoriesConfig,
[DataSourceType.DOCKER_REGISTRIES]: dockerRegistriesConfig,
[DataSourceType.NOTIFICATION_CHANNEL_TYPES]: notificationChannelTypesConfig,
[DataSourceType.NOTIFICATION_CHANNELS]: notificationChannelsConfig,
[DataSourceType.USERS]: usersConfig,
[DataSourceType.ROLES]: rolesConfig,
[DataSourceType.DEPARTMENTS]: departmentsConfig
};
/**
*
* @param type
* @returns undefined
*/
export const getDataSourceConfig = (type: DataSourceType) => {
return DATA_SOURCE_REGISTRY[type];
};
/**
*
* @param type
* @returns
*/
export const hasDataSource = (type: DataSourceType): boolean => {
return type in DATA_SOURCE_REGISTRY;
};
/**
*
* @returns
*/
export const getAllDataSourceTypes = (): DataSourceType[] => {
return Object.keys(DATA_SOURCE_REGISTRY) as DataSourceType[];
};

View File

@ -0,0 +1,74 @@
/**
*
*
*/
import request from '@/utils/request';
import { DataSourceType, type DataSourceOption } from './types';
import { getDataSourceConfig } from './DataSourceRegistry';
/**
*
*/
class DataSourceService {
/**
*
* @param type
* @returns
*/
async load(type: DataSourceType): Promise<DataSourceOption[]> {
const config = getDataSourceConfig(type);
if (!config) {
console.error(`❌ 数据源类型 ${type} 未配置`);
return [];
}
try {
const response = await request.get(config.url, { params: config.params });
// request 拦截器已经提取了 data 字段response 直接是数组
return config.transform(response || []);
} catch (error) {
console.error(`❌ 加载数据源 ${type} 失败:`, error);
return [];
}
}
/**
*
* @param types
* @returns
*/
async loadMultiple(
types: DataSourceType[]
): Promise<Record<DataSourceType, DataSourceOption[]>> {
const results = await Promise.all(
types.map(type => this.load(type))
);
return types.reduce((acc, type, index) => {
acc[type] = results[index];
return acc;
}, {} as Record<DataSourceType, DataSourceOption[]>);
}
/**
*
* @returns
*/
async preload(): Promise<Record<DataSourceType, DataSourceOption[]>> {
const commonTypes: DataSourceType[] = [
DataSourceType.JENKINS_SERVERS,
DataSourceType.K8S_CLUSTERS,
DataSourceType.USERS
];
return this.loadMultiple(commonTypes);
}
}
// 导出单例
export const dataSourceService = new DataSourceService();
// 向后兼容导出原有的函数式API
export const loadDataSource = (type: DataSourceType) => dataSourceService.load(type);
export const loadMultipleDataSources = (types: DataSourceType[]) => dataSourceService.loadMultiple(types);

View File

@ -0,0 +1,15 @@
/**
*
*
*/
// 类型定义
export { DataSourceType } from './types';
export type { DataSourceOption, DataSourceConfig, DataSourceRegistry } from './types';
// 注册表
export { DATA_SOURCE_REGISTRY, getDataSourceConfig, hasDataSource, getAllDataSourceTypes } from './DataSourceRegistry';
// 服务
export { dataSourceService, loadDataSource, loadMultipleDataSources } from './DataSourceService';

View File

@ -0,0 +1,17 @@
/**
* Docker
*/
import type { DataSourceConfig } from '../types';
export const dockerRegistriesConfig: DataSourceConfig = {
url: '/api/v1/docker-registry/list',
params: { enabled: true },
transform: (data: any[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
url: item.url
}));
}
};

View File

@ -0,0 +1,17 @@
/**
* Git
*/
import type { DataSourceConfig } from '../types';
export const gitRepositoriesConfig: DataSourceConfig = {
url: '/api/v1/git-repo/list',
params: { enabled: true },
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.name} (${item.url})`,
value: item.id,
url: item.url
}));
}
};

View File

@ -0,0 +1,18 @@
/**
* Jenkins
*/
import type { DataSourceConfig } from '../types';
export const jenkinsServersConfig: DataSourceConfig = {
url: '/api/v1/external-system/list',
params: { type: 'JENKINS', enabled: true },
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.name} (${item.url})`,
value: item.id,
url: item.url,
name: item.name
}));
}
};

View File

@ -0,0 +1,17 @@
/**
* Kubernetes
*/
import type { DataSourceConfig } from '../types';
export const k8sClustersConfig: DataSourceConfig = {
url: '/api/v1/k8s-cluster/list',
params: { enabled: true },
transform: (data: any[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
apiServer: item.apiServer
}));
}
};

View File

@ -0,0 +1,25 @@
/**
*
*/
import type { DataSourceConfig } from '../types';
export const notificationChannelTypesConfig: DataSourceConfig = {
url: '/api/v1/notification-channel/types',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.label}`,
value: item.code
}));
}
};
export const notificationChannelsConfig: DataSourceConfig = {
url: '/api/v1/notification-channel/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `(${item.channelType})-${item.name}`,
value: item.id
}));
}
};

View File

@ -0,0 +1,43 @@
/**
*
*/
import type { DataSourceConfig } from '../types';
export const usersConfig: DataSourceConfig = {
url: '/api/v1/user/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.nickname} (${item.username})`,
value: item.username, // 后台使用 username 进行审批
id: item.id,
email: item.email,
departmentName: item.departmentName
}));
}
};
export const rolesConfig: DataSourceConfig = {
url: '/api/v1/role/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.name} (${item.code})`,
value: item.code, // 使用 code 作为值
id: item.id,
description: item.description
}));
}
};
export const departmentsConfig: DataSourceConfig = {
url: '/api/v1/department/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.name} (${item.code})`,
value: item.code, // 使用 code 作为值
id: item.id,
description: item.description,
parentId: item.parentId
}));
}
};

View File

@ -0,0 +1,42 @@
/**
* -
*/
/**
*
*/
export enum DataSourceType {
JENKINS_SERVERS = 'JENKINS_SERVERS',
K8S_CLUSTERS = 'K8S_CLUSTERS',
GIT_REPOSITORIES = 'GIT_REPOSITORIES',
DOCKER_REGISTRIES = 'DOCKER_REGISTRIES',
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES',
NOTIFICATION_CHANNELS = 'NOTIFICATION_CHANNELS',
USERS = 'USERS',
ROLES = 'ROLES',
DEPARTMENTS = 'DEPARTMENTS'
}
/**
*
*/
export interface DataSourceOption {
label: string;
value: any;
[key: string]: any;
}
/**
*
*/
export interface DataSourceConfig {
url: string;
params?: Record<string, any>;
transform: (data: any) => DataSourceOption[];
}
/**
*
*/
export type DataSourceRegistry = Record<DataSourceType, DataSourceConfig>;

View File

@ -19,7 +19,7 @@ import {
} from 'antd'; } from 'antd';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons'; import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import type { FieldConfig, FormConfig } from '../types'; import type { FieldConfig, FormConfig } from '../types';
import { DataSourceType } from '@/pages/Workflow/Design/utils/dataSourceLoader'; import { DataSourceType } from '@/domain/dataSource';
const { Text } = Typography; const { Text } = Typography;
const { TabPane } = Tabs; const { TabPane } = Tabs;

View File

@ -6,7 +6,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import type { FieldConfig, FieldOption } from '../types'; import type { FieldConfig, FieldOption } from '../types';
import request from '../../../utils/request'; import request from '../../../utils/request';
import { loadDataSource, DataSourceType as WorkflowDataSourceType } from '@/pages/Workflow/Design/utils/dataSourceLoader'; import { loadDataSource, DataSourceType as WorkflowDataSourceType } from '@/domain/dataSource';
/** /**
* *

View File

@ -19,7 +19,7 @@ import type { FlowNode, FlowNodeData, FlowEdge } from '../types';
import type { WorkflowNodeDefinition, JSONSchema } from '../nodes/types'; import type { WorkflowNodeDefinition, JSONSchema } from '../nodes/types';
import { isConfigurableNode } from '../nodes/types'; import { isConfigurableNode } from '../nodes/types';
import { convertJsonSchemaToZod, extractDataSourceTypes } from '../utils/schemaConverter'; import { convertJsonSchemaToZod, extractDataSourceTypes } from '../utils/schemaConverter';
import { loadDataSource, DataSourceType, type DataSourceOption } from '../utils/dataSourceLoader'; import { loadDataSource, DataSourceType, type DataSourceOption } from '@/domain/dataSource';
import { convertObjectToUUID, convertObjectToDisplayName } from '@/utils/workflow/variableConversion'; import { convertObjectToUUID, convertObjectToDisplayName } from '@/utils/workflow/variableConversion';
import VariableInput from '@/components/VariableInput'; import VariableInput from '@/components/VariableInput';

View File

@ -1,5 +1,5 @@
import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types'; import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types';
import {DataSourceType} from '../utils/dataSourceLoader'; import { DataSourceType } from '@/domain/dataSource';
/** /**
* *

View File

@ -1,5 +1,5 @@
import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types'; import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types';
import {DataSourceType} from '../utils/dataSourceLoader'; import { DataSourceType } from '@/domain/dataSource';
/** /**
* Jenkins构建节点定义 * Jenkins构建节点定义

View File

@ -1,5 +1,5 @@
import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types'; import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types';
import {DataSourceType} from "@/pages/Workflow/Design/utils/dataSourceLoader.ts"; import { DataSourceType } from "@/domain/dataSource";
/** /**
* *

View File

@ -1,181 +0,0 @@
import request from '@/utils/request';
/**
*
*/
export enum DataSourceType {
JENKINS_SERVERS = 'JENKINS_SERVERS',
K8S_CLUSTERS = 'K8S_CLUSTERS',
GIT_REPOSITORIES = 'GIT_REPOSITORIES',
DOCKER_REGISTRIES = 'DOCKER_REGISTRIES',
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES',
NOTIFICATION_CHANNELS = 'NOTIFICATION_CHANNELS',
USERS = 'USERS',
ROLES = 'ROLES',
DEPARTMENTS = 'DEPARTMENTS'
}
/**
*
*/
export interface DataSourceConfig {
url: string;
params?: Record<string, any>;
transform: (data: any) => Array<{ label: string; value: any; [key: string]: any }>;
}
/**
*
*/
export interface DataSourceOption {
label: string;
value: any;
[key: string]: any;
}
/**
*
*/
export const DATA_SOURCE_REGISTRY: Record<DataSourceType, DataSourceConfig> = {
[DataSourceType.JENKINS_SERVERS]: {
url: '/api/v1/external-system/list',
params: {type: 'JENKINS', enabled: true},
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.name} (${item.url})`,
value: item.id,
url: item.url,
name: item.name
}));
}
},
[DataSourceType.NOTIFICATION_CHANNEL_TYPES]: {
url: '/api/v1/notification-channel/types',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.label}`,
value: item.code
}));
}
},
[DataSourceType.NOTIFICATION_CHANNELS]: {
url: '/api/v1/notification-channel/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `(${item.channelType})-${item.name}`,
value: item.id
}));
}
},
[DataSourceType.K8S_CLUSTERS]: {
url: '/api/v1/k8s-cluster/list',
params: {enabled: true},
transform: (data: any[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
apiServer: item.apiServer
}));
}
},
[DataSourceType.GIT_REPOSITORIES]: {
url: '/api/v1/git-repo/list',
params: {enabled: true},
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.name} (${item.url})`,
value: item.id,
url: item.url
}));
}
},
[DataSourceType.DOCKER_REGISTRIES]: {
url: '/api/v1/docker-registry/list',
params: {enabled: true},
transform: (data: any[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
url: item.url
}));
}
},
[DataSourceType.USERS]: {
url: '/api/v1/user/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.nickname} (${item.username})`,
value: item.username, // 后台使用 username 进行审批
id: item.id,
email: item.email,
departmentName: item.departmentName
}));
}
},
[DataSourceType.ROLES]: {
url: '/api/v1/role/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.name} (${item.code})`,
value: item.code, // 使用 code 作为值
id: item.id,
description: item.description
}));
}
},
[DataSourceType.DEPARTMENTS]: {
url: '/api/v1/department/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.name} (${item.code})`,
value: item.code, // 使用 code 作为值
id: item.id,
description: item.description,
parentId: item.parentId
}));
}
}
};
/**
*
* @param type
* @returns
*/
export const loadDataSource = async (type: DataSourceType): Promise<DataSourceOption[]> => {
const config = DATA_SOURCE_REGISTRY[type];
if (!config) {
console.error(`数据源类型 ${type} 未配置`);
return [];
}
try {
const response = await request.get(config.url, {params: config.params});
// request 拦截器已经提取了 data 字段response 直接是数组
return config.transform(response || []);
} catch (error) {
console.error(`加载数据源 ${type} 失败:`, error);
return [];
}
};
/**
*
* @param types
* @returns
*/
export const loadMultipleDataSources = async (
types: DataSourceType[]
): Promise<Record<DataSourceType, DataSourceOption[]>> => {
const results = await Promise.all(
types.map(type => loadDataSource(type))
);
return types.reduce((acc, type, index) => {
acc[type] = results[index];
return acc;
}, {} as Record<DataSourceType, DataSourceOption[]>);
};