1
This commit is contained in:
parent
684e682bcd
commit
64220aef44
@ -155,6 +155,25 @@ export const getCurrentUserMenus = async () => {
|
||||
]
|
||||
};
|
||||
|
||||
// 添加X6测试菜单
|
||||
// const x6Test: MenuResponse = {
|
||||
// id: -1,
|
||||
// createTime: new Date().toISOString(),
|
||||
// updateTime: new Date().toISOString(),
|
||||
// version: 0,
|
||||
// name: "X6测试",
|
||||
// path: "/x6-test",
|
||||
// component: "/X6Test/index",
|
||||
// icon: "experiment",
|
||||
// type: MenuTypeEnum.MENU,
|
||||
// parentId: 0,
|
||||
// sort: 1,
|
||||
// hidden: false,
|
||||
// enabled: true,
|
||||
// createBy: "system",
|
||||
// updateBy: "system"
|
||||
// };
|
||||
|
||||
// 处理组件路径格式
|
||||
const processMenu = (menu: MenuResponse): MenuResponse => {
|
||||
const processed = { ...menu };
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Row, Col, Menu, Tabs, Card } from 'antd';
|
||||
import { BetaSchemaForm, ProForm, ProFormGroup } from '@ant-design/pro-form';
|
||||
import { Row, Col, Menu, Tabs, Card, Button, message } from 'antd';
|
||||
import { BetaSchemaForm, ProForm, ProFormGroup, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
|
||||
import type { NodeDefinitionData } from './types';
|
||||
import * as service from './service';
|
||||
|
||||
@ -22,6 +22,9 @@ const NodeDesignForm: React.FC = () => {
|
||||
// 当前选中的 tab
|
||||
const [activeTab, setActiveTab] = useState<string>('panel');
|
||||
|
||||
const formRef = useRef<any>();
|
||||
const uiFormRef = useRef<any>(); // 添加 UI 配置表单的 ref
|
||||
|
||||
// 加载节点定义数据
|
||||
useEffect(() => {
|
||||
const loadNodeDefinitions = async () => {
|
||||
@ -173,6 +176,22 @@ const NodeDesignForm: React.FC = () => {
|
||||
'boolean': 'switch'
|
||||
};
|
||||
|
||||
// 判断是否是颜色字段
|
||||
const isColorField = value.type === 'string' &&
|
||||
(value.title?.includes('颜色') || value.description?.includes('颜色'));
|
||||
|
||||
if (isColorField) {
|
||||
return {
|
||||
...baseField,
|
||||
valueType: 'color',
|
||||
placeholder: `请选择${value.title}`,
|
||||
fieldProps: {
|
||||
showText: true,
|
||||
format: 'hex'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...baseField,
|
||||
valueType: typeMap[value.type] || 'text',
|
||||
@ -182,84 +201,146 @@ const NodeDesignForm: React.FC = () => {
|
||||
};
|
||||
|
||||
// 渲染 Tab 内容
|
||||
const renderTabContent = (tabKey: string) => {
|
||||
const renderTabContent = (schemaKey: string) => {
|
||||
if (!selectedNode) return null;
|
||||
|
||||
const content = selectedNode[tabKey as keyof NodeDefinitionData];
|
||||
if (!content) return null;
|
||||
|
||||
const currentTab = TAB_CONFIG.find(tab => tab.schemaKey === tabKey);
|
||||
const isReadOnly = currentTab?.readonly ?? true;
|
||||
|
||||
// 判断是否是 schema 格式
|
||||
const isSchema = typeof content === 'object' && 'type' in content && 'properties' in content;
|
||||
console.log('Is Schema:', isSchema);
|
||||
console.log('Content:', content);
|
||||
|
||||
const schema = isSchema ? content : {
|
||||
type: 'object',
|
||||
properties: Object.entries(content).reduce((acc, [key, value]) => ({
|
||||
...acc,
|
||||
[key]: {
|
||||
title: key,
|
||||
type: typeof value,
|
||||
default: value
|
||||
}
|
||||
}), {})
|
||||
};
|
||||
|
||||
console.log('Final Schema:', schema);
|
||||
// 获取默认值
|
||||
const defaultValues = getDefaultValues(schema);
|
||||
console.log('Default Values:', defaultValues);
|
||||
const schema = selectedNode[schemaKey as keyof NodeDefinitionData];
|
||||
if (!schema) return null;
|
||||
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
<BetaSchemaForm
|
||||
formRef={schemaKey === 'uiVariables' ? uiFormRef : undefined}
|
||||
layoutType="Form"
|
||||
readonly={isReadOnly}
|
||||
onFinish={handleFormSubmit}
|
||||
initialValues={getDefaultValues(schema)}
|
||||
columns={convertToProFormSchema(schema)}
|
||||
initialValues={defaultValues}
|
||||
submitter={!isReadOnly}
|
||||
grid={false}
|
||||
layout="vertical"
|
||||
style={{ maxWidth: '100%' }}
|
||||
formItemProps={{
|
||||
labelCol: { span: 24 },
|
||||
wrapperCol: { span: 24 },
|
||||
style: {
|
||||
marginBottom: 16
|
||||
}
|
||||
}}
|
||||
submitter={false}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// 将扁平的对象转换为嵌套对象
|
||||
const flatToNested = (obj: Record<string, any>) => {
|
||||
const result: Record<string, any> = {};
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
const parts = key.split('.');
|
||||
let current = result;
|
||||
|
||||
parts.forEach((part, index) => {
|
||||
if (index === parts.length - 1) {
|
||||
current[part] = obj[key];
|
||||
} else {
|
||||
current[part] = current[part] || {};
|
||||
current = current[part];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!selectedNode) return;
|
||||
|
||||
try {
|
||||
const formValues = await formRef.current?.validateFields();
|
||||
const uiValues = await uiFormRef.current?.validateFields();
|
||||
|
||||
const saveData = {
|
||||
nodeType: formValues.nodeType,
|
||||
nodeCode: formValues.nodeCode,
|
||||
nodeName: formValues.nodeName,
|
||||
category: formValues.category,
|
||||
description: formValues.description,
|
||||
uiVariables: flatToNested(uiValues), // 将扁平结构转换为嵌套结构
|
||||
localVariablesSchema: selectedNode.localVariablesSchema,
|
||||
panelVariablesSchema: selectedNode.panelVariablesSchema,
|
||||
formVariablesSchema: selectedNode.formVariablesSchema
|
||||
};
|
||||
|
||||
await service.saveNodeDefinition(saveData);
|
||||
message.success('保存成功');
|
||||
} catch (error) {
|
||||
console.error('Save failed:', error);
|
||||
message.error('保存失败');
|
||||
}
|
||||
};
|
||||
|
||||
const renderBasicForm = () => {
|
||||
if (!selectedNode) return null;
|
||||
|
||||
return (
|
||||
<ProForm
|
||||
formRef={formRef}
|
||||
key={selectedNode.nodeCode}
|
||||
layout="horizontal"
|
||||
submitter={false}
|
||||
initialValues={{
|
||||
nodeType: selectedNode.nodeCode,
|
||||
nodeCode: selectedNode.nodeCode,
|
||||
nodeName: selectedNode.nodeName,
|
||||
category: selectedNode.category,
|
||||
}}
|
||||
>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="nodeType"
|
||||
label="节点类型"
|
||||
disabled
|
||||
colProps={{ span: 12 }}
|
||||
/>
|
||||
<ProFormText
|
||||
name="nodeCode"
|
||||
label="节点编码"
|
||||
disabled
|
||||
colProps={{ span: 12 }}
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="nodeName"
|
||||
label="节点名称"
|
||||
disabled
|
||||
colProps={{ span: 12 }}
|
||||
/>
|
||||
<ProFormText
|
||||
name="category"
|
||||
label="节点类别"
|
||||
disabled
|
||||
colProps={{ span: 12 }}
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProFormTextArea
|
||||
name="description"
|
||||
label="节点描述"
|
||||
placeholder="请输入节点描述"
|
||||
colProps={{ span: 24 }}
|
||||
/>
|
||||
</ProForm.Group>
|
||||
</ProForm>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
loading={loading}
|
||||
header={{
|
||||
title: '节点设计',
|
||||
breadcrumb: {
|
||||
items: [
|
||||
{ title: '工作流' },
|
||||
{ title: '节点设计', path: '/workflow/node-design' }
|
||||
],
|
||||
},
|
||||
}}
|
||||
extra={[
|
||||
<Button
|
||||
key="save"
|
||||
type="primary"
|
||||
onClick={handleSave}
|
||||
disabled={!selectedNode}
|
||||
>
|
||||
<div style={{ background: '#fff', minHeight: 'calc(100vh - 200px)' }}>
|
||||
<Row>
|
||||
<Col span={5} style={{ borderRight: '1px solid #f0f0f0', padding: '16px 0' }}>
|
||||
保存
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<div style={{ display: 'flex', height: '100%' }}>
|
||||
<div style={{ width: 200, borderRight: '1px solid #f0f0f0', marginRight: 24 }}>
|
||||
<div style={{
|
||||
padding: '0 24px 16px',
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
color: '#000000d9',
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
marginBottom: '8px'
|
||||
padding: '8px 16px',
|
||||
fontWeight: 'bold',
|
||||
borderBottom: '1px solid #f0f0f0'
|
||||
}}>
|
||||
节点类型
|
||||
</div>
|
||||
@ -268,7 +349,7 @@ const NodeDesignForm: React.FC = () => {
|
||||
selectedKeys={[selectedNode?.nodeCode || '']}
|
||||
items={nodeDefinitions.map(node => ({
|
||||
key: node.nodeCode,
|
||||
label: node.nodeName,
|
||||
label: `${node.nodeName}(${node.nodeCode})`,
|
||||
}))}
|
||||
onClick={({ key }) => handleNodeSelect(key)}
|
||||
style={{
|
||||
@ -276,33 +357,19 @@ const NodeDesignForm: React.FC = () => {
|
||||
padding: '0 16px'
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={19}>
|
||||
<div style={{ padding: '16px 24px' }}>
|
||||
{selectedNode && getAvailableTabs(selectedNode).length > 0 ? (
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
{renderBasicForm()}
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={handleTabChange}
|
||||
type="card"
|
||||
items={getAvailableTabs(selectedNode).map(tab => ({
|
||||
key: tab.key,
|
||||
key: tab.schemaKey,
|
||||
label: tab.label,
|
||||
children: renderTabContent(tab.schemaKey)
|
||||
}))}
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '32px',
|
||||
color: '#00000073',
|
||||
fontSize: '14px'
|
||||
}}>
|
||||
暂无可配置项
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
@ -100,20 +100,13 @@ const NodeDesignList: React.FC = () => {
|
||||
...rest,
|
||||
});
|
||||
return {
|
||||
data: response.list,
|
||||
data: response.content,
|
||||
success: true,
|
||||
total: response.total,
|
||||
total: response.totalElements
|
||||
};
|
||||
}}
|
||||
rowKey="nodeCode"
|
||||
search={{
|
||||
labelWidth: 'auto',
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
}}
|
||||
dateFormatter="string"
|
||||
headerTitle="节点设计列表"
|
||||
search={false}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
key="create"
|
||||
@ -121,15 +114,7 @@ const NodeDesignList: React.FC = () => {
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => navigate('/workflow/node-design/create')}
|
||||
>
|
||||
新增节点
|
||||
</Button>,
|
||||
<Button
|
||||
key="export"
|
||||
onClick={() => {
|
||||
// TODO: 实现导出功能
|
||||
}}
|
||||
>
|
||||
导出配置
|
||||
新建节点
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -3,20 +3,24 @@
|
||||
import request from '@/utils/request';
|
||||
import type { NodeDesignQuery, NodeDesignResponse, NodeDesignData, NodeDefinitionData } from './types';
|
||||
|
||||
const BASE_URL = '/api/v1/workflow';
|
||||
const BASE_URL = '/api/v1/workflow/node-definition';
|
||||
|
||||
// 获取节点设计列表
|
||||
export const getNodeDesigns = (params: NodeDesignQuery) =>
|
||||
request.get<NodeDesignResponse>(`${BASE_URL}/node-design/list`, { params });
|
||||
request.get<NodeDesignResponse>(`${BASE_URL}/page`, { params });
|
||||
|
||||
// 获取节点设计详情
|
||||
export const getNodeDesign = (nodeCode: string) =>
|
||||
request.get<NodeDesignData>(`${BASE_URL}/node-design/${nodeCode}`);
|
||||
request.get<NodeDesignData>(`${BASE_URL}/${nodeCode}`);
|
||||
|
||||
// 更新节点UI配置
|
||||
export const updateNodeUIConfig = (nodeCode: string, uiVariables: any) =>
|
||||
request.put(`${BASE_URL}/node-design/${nodeCode}/ui`, uiVariables);
|
||||
request.put(`${BASE_URL}/${nodeCode}/ui`, uiVariables);
|
||||
|
||||
// 获取已定义的节点类型配置
|
||||
export const getNodeDefinitionsDefined = () =>
|
||||
request.get<NodeDefinitionData[]>(`${BASE_URL}/node-definition/defined`);
|
||||
request.get<NodeDefinitionData[]>(`${BASE_URL}/defined`);
|
||||
|
||||
// 保存节点定义
|
||||
export const saveNodeDefinition = (data: NodeDefinitionData) =>
|
||||
request.post<void>(`${BASE_URL}`, data);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// 节点设计相关类型定义
|
||||
|
||||
// 基础Schema接口
|
||||
import {BaseQuery, BaseResponse} from "@/types/base";
|
||||
|
||||
export interface BaseSchema {
|
||||
type: string;
|
||||
properties: Record<string, any>;
|
||||
@ -68,20 +70,11 @@ export interface UIVariables extends BaseSchema {
|
||||
style: NodeStyle;
|
||||
}
|
||||
|
||||
// 节点定义数据接口
|
||||
export interface NodeDefinitionData {
|
||||
nodeCode: string;
|
||||
nodeName: string;
|
||||
panelVariablesSchema: NodeVariablesSchema | null;
|
||||
localVariablesSchema: NodeVariablesSchema | null;
|
||||
formVariablesSchema: NodeVariablesSchema | null;
|
||||
uiVariables: UIVariables;
|
||||
}
|
||||
|
||||
// 节点设计数据
|
||||
export interface NodeDesignData {
|
||||
nodeCode: string;
|
||||
nodeName: string;
|
||||
category: string;
|
||||
panelVariablesSchema: NodeVariablesSchema | null;
|
||||
localVariablesSchema: NodeVariablesSchema | null;
|
||||
formVariablesSchema: NodeVariablesSchema | null;
|
||||
@ -96,15 +89,12 @@ export enum NodeTypeEnum {
|
||||
}
|
||||
|
||||
// 查询参数接口
|
||||
export interface NodeDesignQuery {
|
||||
export interface NodeDesignQuery extends BaseQuery{
|
||||
nodeCode?: string;
|
||||
nodeName?: string;
|
||||
pageSize?: number;
|
||||
current?: number;
|
||||
}
|
||||
|
||||
// 分页响应接口
|
||||
export interface NodeDesignResponse {
|
||||
total: number;
|
||||
list: NodeDesignData[];
|
||||
export interface NodeDesignResponse extends BaseResponse{
|
||||
|
||||
}
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { message } from 'antd';
|
||||
|
||||
interface Response<T = any> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
class Http {
|
||||
private instance: AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
this.instance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
this.setupInterceptors();
|
||||
}
|
||||
|
||||
private setupInterceptors() {
|
||||
// 请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
(response: AxiosResponse<Response>) => {
|
||||
const { data: res } = response;
|
||||
if (res.success) {
|
||||
return res.data;
|
||||
}
|
||||
message.error(res.message);
|
||||
return Promise.reject(new Error(res.message));
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// 处理未授权
|
||||
localStorage.removeItem('token');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
message.error(error.response?.data?.message || '网络错误');
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
get<T = any>(url: string, config?: AxiosRequestConfig) {
|
||||
return this.instance.get<Response<T>, T>(url, config);
|
||||
}
|
||||
|
||||
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig) {
|
||||
return this.instance.post<Response<T>, T>(url, data, config);
|
||||
}
|
||||
|
||||
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig) {
|
||||
return this.instance.put<Response<T>, T>(url, data, config);
|
||||
}
|
||||
|
||||
delete<T = any>(url: string, config?: AxiosRequestConfig) {
|
||||
return this.instance.delete<Response<T>, T>(url, config);
|
||||
}
|
||||
|
||||
download(url: string, filename?: string, config?: AxiosRequestConfig) {
|
||||
return this.instance
|
||||
.get(url, { ...config, responseType: 'blob' })
|
||||
.then((response) => {
|
||||
const blob = new Blob([response]);
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = filename || 'download';
|
||||
link.click();
|
||||
window.URL.revokeObjectURL(link.href);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const http = new Http();
|
||||
Loading…
Reference in New Issue
Block a user