From 64220aef4478e3706aa6b4c08bf8686c65c182c5 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Thu, 19 Dec 2024 18:18:06 +0800 Subject: [PATCH] 1 --- frontend/src/pages/System/Menu/service.ts | 19 ++ .../src/pages/Workflow/NodeDesign/Design.tsx | 295 +++++++++++------- .../src/pages/Workflow/NodeDesign/index.tsx | 23 +- .../src/pages/Workflow/NodeDesign/service.ts | 14 +- .../src/pages/Workflow/NodeDesign/types.ts | 22 +- frontend/src/utils/http.ts | 94 ------ 6 files changed, 219 insertions(+), 248 deletions(-) delete mode 100644 frontend/src/utils/http.ts diff --git a/frontend/src/pages/System/Menu/service.ts b/frontend/src/pages/System/Menu/service.ts index a8883d11..eed74192 100644 --- a/frontend/src/pages/System/Menu/service.ts +++ b/frontend/src/pages/System/Menu/service.ts @@ -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 }; diff --git a/frontend/src/pages/Workflow/NodeDesign/Design.tsx b/frontend/src/pages/Workflow/NodeDesign/Design.tsx index b643f0d4..8841bfd0 100644 --- a/frontend/src/pages/Workflow/NodeDesign/Design.tsx +++ b/frontend/src/pages/Workflow/NodeDesign/Design.tsx @@ -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('panel'); + const formRef = useRef(); + const uiFormRef = useRef(); // 添加 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,127 +201,175 @@ 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 ( - - - + + ); + }; + + // 将扁平的对象转换为嵌套对象 + const flatToNested = (obj: Record) => { + const result: Record = {}; + + 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 ( + + + + + + + + + + + + + ); }; return ( + 保存 + + ]} > -
- - -
- 节点类型 -
- ({ - key: node.nodeCode, - label: node.nodeName, - }))} - onClick={({ key }) => handleNodeSelect(key)} - style={{ - border: 'none', - padding: '0 16px' - }} - /> - - -
- {selectedNode && getAvailableTabs(selectedNode).length > 0 ? ( - ({ - key: tab.key, - label: tab.label, - children: renderTabContent(tab.schemaKey) - }))} - /> - ) : ( -
- 暂无可配置项 -
- )} -
- - +
+
+
+ 节点类型 +
+ ({ + key: node.nodeCode, + label: `${node.nodeName}(${node.nodeCode})`, + }))} + onClick={({ key }) => handleNodeSelect(key)} + style={{ + border: 'none', + padding: '0 16px' + }} + /> +
+
+ {renderBasicForm()} + ({ + key: tab.schemaKey, + label: tab.label, + children: renderTabContent(tab.schemaKey) + }))} + /> +
); diff --git a/frontend/src/pages/Workflow/NodeDesign/index.tsx b/frontend/src/pages/Workflow/NodeDesign/index.tsx index 8536c6e9..38bf427f 100644 --- a/frontend/src/pages/Workflow/NodeDesign/index.tsx +++ b/frontend/src/pages/Workflow/NodeDesign/index.tsx @@ -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={() => [ , - , ]} /> diff --git a/frontend/src/pages/Workflow/NodeDesign/service.ts b/frontend/src/pages/Workflow/NodeDesign/service.ts index 0bb1c7f6..42c1c431 100644 --- a/frontend/src/pages/Workflow/NodeDesign/service.ts +++ b/frontend/src/pages/Workflow/NodeDesign/service.ts @@ -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(`${BASE_URL}/node-design/list`, { params }); + request.get(`${BASE_URL}/page`, { params }); // 获取节点设计详情 export const getNodeDesign = (nodeCode: string) => - request.get(`${BASE_URL}/node-design/${nodeCode}`); + request.get(`${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(`${BASE_URL}/node-definition/defined`); + request.get(`${BASE_URL}/defined`); + +// 保存节点定义 +export const saveNodeDefinition = (data: NodeDefinitionData) => + request.post(`${BASE_URL}`, data); diff --git a/frontend/src/pages/Workflow/NodeDesign/types.ts b/frontend/src/pages/Workflow/NodeDesign/types.ts index ef888dbf..69c98876 100644 --- a/frontend/src/pages/Workflow/NodeDesign/types.ts +++ b/frontend/src/pages/Workflow/NodeDesign/types.ts @@ -1,6 +1,8 @@ // 节点设计相关类型定义 // 基础Schema接口 +import {BaseQuery, BaseResponse} from "@/types/base"; + export interface BaseSchema { type: string; properties: Record; @@ -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{ + } diff --git a/frontend/src/utils/http.ts b/frontend/src/utils/http.ts deleted file mode 100644 index 951c5f75..00000000 --- a/frontend/src/utils/http.ts +++ /dev/null @@ -1,94 +0,0 @@ -import axios from 'axios'; -import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; -import { message } from 'antd'; - -interface Response { - 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) => { - 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(url: string, config?: AxiosRequestConfig) { - return this.instance.get, T>(url, config); - } - - post(url: string, data?: any, config?: AxiosRequestConfig) { - return this.instance.post, T>(url, data, config); - } - - put(url: string, data?: any, config?: AxiosRequestConfig) { - return this.instance.put, T>(url, data, config); - } - - delete(url: string, config?: AxiosRequestConfig) { - return this.instance.delete, 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(); \ No newline at end of file