增加审批组件
This commit is contained in:
parent
f8583ba3ef
commit
e340e3106d
@ -18,6 +18,7 @@ const VariableInput: React.FC<VariableInputProps> = ({
|
|||||||
allNodes,
|
allNodes,
|
||||||
allEdges,
|
allEdges,
|
||||||
currentNodeId,
|
currentNodeId,
|
||||||
|
formFields = [],
|
||||||
variant = 'input',
|
variant = 'input',
|
||||||
placeholder,
|
placeholder,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
@ -32,10 +33,23 @@ const VariableInput: React.FC<VariableInputProps> = ({
|
|||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
const highlightRef = useRef<HTMLDivElement>(null);
|
const highlightRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// 收集可用变量
|
// 将表单字段转换为变量格式(匹配 NodeVariable 结构)
|
||||||
|
const formVariables = useMemo(() => {
|
||||||
|
return formFields.map(field => ({
|
||||||
|
nodeId: 'form',
|
||||||
|
nodeName: '启动表单',
|
||||||
|
fieldName: field.name,
|
||||||
|
fieldType: field.type,
|
||||||
|
displayText: `\${form.${field.name}}`,
|
||||||
|
fullText: `form.${field.name}`,
|
||||||
|
}));
|
||||||
|
}, [formFields]);
|
||||||
|
|
||||||
|
// 收集可用变量(节点变量 + 表单变量)
|
||||||
const allVariables = useMemo(() => {
|
const allVariables = useMemo(() => {
|
||||||
return collectNodeVariables(currentNodeId, allNodes, allEdges);
|
const nodeVariables = collectNodeVariables(currentNodeId, allNodes, allEdges);
|
||||||
}, [currentNodeId, allNodes, allEdges]);
|
return [...formVariables, ...nodeVariables];
|
||||||
|
}, [currentNodeId, allNodes, allEdges, formVariables]);
|
||||||
|
|
||||||
// 根据搜索文本过滤变量
|
// 根据搜索文本过滤变量
|
||||||
const filteredVariables = useMemo(() => {
|
const filteredVariables = useMemo(() => {
|
||||||
|
|||||||
@ -1,5 +1,22 @@
|
|||||||
import type { FlowNode, FlowEdge } from '@/pages/Workflow/Design/types';
|
import type { FlowNode, FlowEdge } from '@/pages/Workflow/Design/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单字段信息(用于变量引用)
|
||||||
|
*/
|
||||||
|
export interface FormField {
|
||||||
|
/** 字段名称(变量名)*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** 字段标签(显示名称)*/
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
/** 字段类型 */
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
/** 字段描述 */
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 变量输入组件的 Props
|
* 变量输入组件的 Props
|
||||||
*/
|
*/
|
||||||
@ -19,6 +36,9 @@ export interface VariableInputProps {
|
|||||||
/** 当前节点ID(用于过滤前序节点)*/
|
/** 当前节点ID(用于过滤前序节点)*/
|
||||||
currentNodeId: string;
|
currentNodeId: string;
|
||||||
|
|
||||||
|
/** 表单字段列表(用于支持 ${form.xxx} 变量)*/
|
||||||
|
formFields?: FormField[];
|
||||||
|
|
||||||
/** 渲染类型 */
|
/** 渲染类型 */
|
||||||
variant?: 'input' | 'textarea';
|
variant?: 'input' | 'textarea';
|
||||||
|
|
||||||
|
|||||||
@ -11,12 +11,14 @@ import { useToast } from '@/components/ui/use-toast';
|
|||||||
import type { FlowEdge, FlowNode } from '../types';
|
import type { FlowEdge, FlowNode } from '../types';
|
||||||
import { convertToUUID, convertToDisplayName } from '@/utils/workflow/variableConversion';
|
import { convertToUUID, convertToDisplayName } from '@/utils/workflow/variableConversion';
|
||||||
import VariableInput from '@/components/VariableInput';
|
import VariableInput from '@/components/VariableInput';
|
||||||
|
import type { FormField as FormFieldType } from '@/components/VariableInput/types';
|
||||||
|
|
||||||
interface EdgeConfigModalProps {
|
interface EdgeConfigModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
edge: FlowEdge | null;
|
edge: FlowEdge | null;
|
||||||
allNodes: FlowNode[];
|
allNodes: FlowNode[];
|
||||||
allEdges: FlowEdge[];
|
allEdges: FlowEdge[];
|
||||||
|
formFields?: FormFieldType[];
|
||||||
onOk: (edgeId: string, condition: EdgeCondition) => void;
|
onOk: (edgeId: string, condition: EdgeCondition) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
@ -58,6 +60,7 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
|
|||||||
edge,
|
edge,
|
||||||
allNodes,
|
allNodes,
|
||||||
allEdges,
|
allEdges,
|
||||||
|
formFields = [],
|
||||||
onOk,
|
onOk,
|
||||||
onCancel
|
onCancel
|
||||||
}) => {
|
}) => {
|
||||||
@ -200,8 +203,9 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
|
|||||||
allNodes={allNodes}
|
allNodes={allNodes}
|
||||||
allEdges={allEdges}
|
allEdges={allEdges}
|
||||||
currentNodeId={edge?.target || ''}
|
currentNodeId={edge?.target || ''}
|
||||||
|
formFields={formFields}
|
||||||
variant="textarea"
|
variant="textarea"
|
||||||
placeholder="请输入条件表达式,如:${Jenkins构建.buildStatus == 'SUCCESS'}"
|
placeholder="请输入条件表达式,如:${form.环境选择} 或 ${Jenkins构建.buildStatus == 'SUCCESS'}"
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@ -22,12 +22,14 @@ import { convertJsonSchemaToZod, extractDataSourceTypes } from '../utils/schemaC
|
|||||||
import { loadDataSource, DataSourceType, type DataSourceOption } from '@/domain/dataSource';
|
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';
|
||||||
|
import type { FormField as FormFieldType } from '@/components/VariableInput/types';
|
||||||
|
|
||||||
interface NodeConfigModalProps {
|
interface NodeConfigModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
node: FlowNode | null;
|
node: FlowNode | null;
|
||||||
allNodes: FlowNode[];
|
allNodes: FlowNode[];
|
||||||
allEdges: FlowEdge[];
|
allEdges: FlowEdge[];
|
||||||
|
formFields?: FormFieldType[];
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onOk: (nodeId: string, updatedData: Partial<FlowNodeData>) => void;
|
onOk: (nodeId: string, updatedData: Partial<FlowNodeData>) => void;
|
||||||
}
|
}
|
||||||
@ -125,6 +127,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
node,
|
node,
|
||||||
allNodes,
|
allNodes,
|
||||||
allEdges,
|
allEdges,
|
||||||
|
formFields = [],
|
||||||
onCancel,
|
onCancel,
|
||||||
onOk
|
onOk
|
||||||
}) => {
|
}) => {
|
||||||
@ -500,6 +503,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
allNodes={allNodesRef.current}
|
allNodes={allNodesRef.current}
|
||||||
allEdges={allEdgesRef.current}
|
allEdges={allEdgesRef.current}
|
||||||
currentNodeId={node?.id || ''}
|
currentNodeId={node?.id || ''}
|
||||||
|
formFields={formFields}
|
||||||
variant={isTextarea ? 'textarea' : 'input'}
|
variant={isTextarea ? 'textarea' : 'input'}
|
||||||
placeholder={prop.description || `请输入${prop.title || ''}`}
|
placeholder={prop.description || `请输入${prop.title || ''}`}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { ReactFlowProvider, useReactFlow } from '@xyflow/react';
|
import { ReactFlowProvider, useReactFlow } from '@xyflow/react';
|
||||||
@ -57,6 +57,44 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
const [formDefinition, setFormDefinition] = useState<FormDefinitionResponse | null>(null);
|
const [formDefinition, setFormDefinition] = useState<FormDefinitionResponse | null>(null);
|
||||||
const [formPreviewVisible, setFormPreviewVisible] = useState(false);
|
const [formPreviewVisible, setFormPreviewVisible] = useState(false);
|
||||||
|
|
||||||
|
// 提取表单字段(用于变量引用)
|
||||||
|
const formFields = useMemo(() => {
|
||||||
|
if (!formDefinition?.schema?.fields) return [];
|
||||||
|
|
||||||
|
const extractFields = (fields: any[]): any[] => {
|
||||||
|
const result: any[] = [];
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
// 跳过布局组件
|
||||||
|
if (['text', 'divider'].includes(field.type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 栅格布局:递归提取子字段
|
||||||
|
if (field.type === 'grid' && field.children) {
|
||||||
|
for (const column of field.children) {
|
||||||
|
if (Array.isArray(column)) {
|
||||||
|
result.push(...extractFields(column));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通字段
|
||||||
|
result.push({
|
||||||
|
name: field.name,
|
||||||
|
label: field.label || field.name,
|
||||||
|
type: field.type,
|
||||||
|
description: field.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return extractFields(formDefinition.schema.fields);
|
||||||
|
}, [formDefinition]);
|
||||||
|
|
||||||
// 保存和加载hooks
|
// 保存和加载hooks
|
||||||
const { hasUnsavedChanges, saveWorkflow, markUnsaved } = useWorkflowSave();
|
const { hasUnsavedChanges, saveWorkflow, markUnsaved } = useWorkflowSave();
|
||||||
const { workflowDefinition, loadWorkflow } = useWorkflowLoad();
|
const { workflowDefinition, loadWorkflow } = useWorkflowLoad();
|
||||||
@ -564,6 +602,7 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
node={configNode}
|
node={configNode}
|
||||||
allNodes={getNodes() as FlowNode[]}
|
allNodes={getNodes() as FlowNode[]}
|
||||||
allEdges={getEdges() as FlowEdge[]}
|
allEdges={getEdges() as FlowEdge[]}
|
||||||
|
formFields={formFields}
|
||||||
onCancel={handleCloseConfigModal}
|
onCancel={handleCloseConfigModal}
|
||||||
onOk={handleNodeConfigUpdate}
|
onOk={handleNodeConfigUpdate}
|
||||||
/>
|
/>
|
||||||
@ -574,6 +613,7 @@ const WorkflowDesignInner: React.FC = () => {
|
|||||||
edge={configEdge}
|
edge={configEdge}
|
||||||
allNodes={getNodes() as FlowNode[]}
|
allNodes={getNodes() as FlowNode[]}
|
||||||
allEdges={getEdges() as FlowEdge[]}
|
allEdges={getEdges() as FlowEdge[]}
|
||||||
|
formFields={formFields}
|
||||||
onOk={handleEdgeConditionUpdate}
|
onOk={handleEdgeConditionUpdate}
|
||||||
onCancel={handleCloseEdgeConfigModal}
|
onCancel={handleCloseEdgeConfigModal}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -29,6 +29,11 @@ export const convertToUUID = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return displayText.replace(VARIABLE_PATTERN, (match, nodeNameOrId, fieldName) => {
|
return displayText.replace(VARIABLE_PATTERN, (match, nodeNameOrId, fieldName) => {
|
||||||
|
// 特殊处理:表单字段
|
||||||
|
if (nodeNameOrId === '启动表单' || nodeNameOrId === 'form') {
|
||||||
|
return `\${form.${fieldName}}`;
|
||||||
|
}
|
||||||
|
|
||||||
// 尝试通过名称查找节点
|
// 尝试通过名称查找节点
|
||||||
const nodeByName = allNodes.find(n => {
|
const nodeByName = allNodes.find(n => {
|
||||||
const nodeName = n.data?.label || n.data?.nodeDefinition?.nodeName;
|
const nodeName = n.data?.label || n.data?.nodeDefinition?.nodeName;
|
||||||
@ -67,6 +72,11 @@ export const convertToDisplayName = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return uuidText.replace(VARIABLE_PATTERN, (match, nodeIdOrName, fieldName) => {
|
return uuidText.replace(VARIABLE_PATTERN, (match, nodeIdOrName, fieldName) => {
|
||||||
|
// 特殊处理:表单字段
|
||||||
|
if (nodeIdOrName === 'form' || nodeIdOrName === '启动表单') {
|
||||||
|
return `\${启动表单.${fieldName}}`;
|
||||||
|
}
|
||||||
|
|
||||||
// 尝试通过UUID查找节点
|
// 尝试通过UUID查找节点
|
||||||
const nodeById = allNodes.find(n => n.id === nodeIdOrName);
|
const nodeById = allNodes.find(n => n.id === nodeIdOrName);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user