增加审批组件

This commit is contained in:
dengqichen 2025-10-25 01:31:20 +08:00
parent f8583ba3ef
commit e340e3106d
6 changed files with 101 additions and 9 deletions

View File

@ -18,6 +18,7 @@ const VariableInput: React.FC<VariableInputProps> = ({
allNodes,
allEdges,
currentNodeId,
formFields = [],
variant = 'input',
placeholder,
disabled = false,
@ -32,10 +33,23 @@ const VariableInput: React.FC<VariableInputProps> = ({
const dropdownRef = 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(() => {
return collectNodeVariables(currentNodeId, allNodes, allEdges);
}, [currentNodeId, allNodes, allEdges]);
const nodeVariables = collectNodeVariables(currentNodeId, allNodes, allEdges);
return [...formVariables, ...nodeVariables];
}, [currentNodeId, allNodes, allEdges, formVariables]);
// 根据搜索文本过滤变量
const filteredVariables = useMemo(() => {

View File

@ -1,5 +1,22 @@
import type { FlowNode, FlowEdge } from '@/pages/Workflow/Design/types';
/**
*
*/
export interface FormField {
/** 字段名称(变量名)*/
name: string;
/** 字段标签(显示名称)*/
label: string;
/** 字段类型 */
type: string;
/** 字段描述 */
description?: string;
}
/**
* Props
*/
@ -19,6 +36,9 @@ export interface VariableInputProps {
/** 当前节点ID用于过滤前序节点*/
currentNodeId: string;
/** 表单字段列表(用于支持 ${form.xxx} 变量)*/
formFields?: FormField[];
/** 渲染类型 */
variant?: 'input' | 'textarea';

View File

@ -11,12 +11,14 @@ import { useToast } from '@/components/ui/use-toast';
import type { FlowEdge, FlowNode } from '../types';
import { convertToUUID, convertToDisplayName } from '@/utils/workflow/variableConversion';
import VariableInput from '@/components/VariableInput';
import type { FormField as FormFieldType } from '@/components/VariableInput/types';
interface EdgeConfigModalProps {
visible: boolean;
edge: FlowEdge | null;
allNodes: FlowNode[];
allEdges: FlowEdge[];
formFields?: FormFieldType[];
onOk: (edgeId: string, condition: EdgeCondition) => void;
onCancel: () => void;
}
@ -58,6 +60,7 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
edge,
allNodes,
allEdges,
formFields = [],
onOk,
onCancel
}) => {
@ -200,9 +203,10 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
allNodes={allNodes}
allEdges={allEdges}
currentNodeId={edge?.target || ''}
formFields={formFields}
variant="textarea"
placeholder="请输入条件表达式,如:${Jenkins构建.buildStatus == 'SUCCESS'}"
rows={4}
placeholder="请输入条件表达式,如:${form.环境选择} 或 ${Jenkins构建.buildStatus == 'SUCCESS'}"
rows={4}
/>
</FormControl>
<FormDescription>

View File

@ -22,12 +22,14 @@ import { convertJsonSchemaToZod, extractDataSourceTypes } from '../utils/schemaC
import { loadDataSource, DataSourceType, type DataSourceOption } from '@/domain/dataSource';
import { convertObjectToUUID, convertObjectToDisplayName } from '@/utils/workflow/variableConversion';
import VariableInput from '@/components/VariableInput';
import type { FormField as FormFieldType } from '@/components/VariableInput/types';
interface NodeConfigModalProps {
visible: boolean;
node: FlowNode | null;
allNodes: FlowNode[];
allEdges: FlowEdge[];
formFields?: FormFieldType[];
onCancel: () => void;
onOk: (nodeId: string, updatedData: Partial<FlowNodeData>) => void;
}
@ -125,6 +127,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
node,
allNodes,
allEdges,
formFields = [],
onCancel,
onOk
}) => {
@ -500,6 +503,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
allNodes={allNodesRef.current}
allEdges={allEdgesRef.current}
currentNodeId={node?.id || ''}
formFields={formFields}
variant={isTextarea ? 'textarea' : 'input'}
placeholder={prop.description || `请输入${prop.title || ''}`}
disabled={loading}

View File

@ -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 { message } from 'antd';
import { ReactFlowProvider, useReactFlow } from '@xyflow/react';
@ -57,6 +57,44 @@ const WorkflowDesignInner: React.FC = () => {
const [formDefinition, setFormDefinition] = useState<FormDefinitionResponse | null>(null);
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
const { hasUnsavedChanges, saveWorkflow, markUnsaved } = useWorkflowSave();
const { workflowDefinition, loadWorkflow } = useWorkflowLoad();
@ -560,13 +598,14 @@ const WorkflowDesignInner: React.FC = () => {
{/* 节点配置弹窗 */}
<NodeConfigModal
visible={configModalVisible}
visible={configModalVisible}
node={configNode}
allNodes={getNodes() as FlowNode[]}
allEdges={getEdges() as FlowEdge[]}
formFields={formFields}
onCancel={handleCloseConfigModal}
onOk={handleNodeConfigUpdate}
/>
onOk={handleNodeConfigUpdate}
/>
{/* 边条件配置弹窗 */}
<EdgeConfigModal
@ -574,6 +613,7 @@ const WorkflowDesignInner: React.FC = () => {
edge={configEdge}
allNodes={getNodes() as FlowNode[]}
allEdges={getEdges() as FlowEdge[]}
formFields={formFields}
onOk={handleEdgeConditionUpdate}
onCancel={handleCloseEdgeConfigModal}
/>

View File

@ -29,6 +29,11 @@ export const convertToUUID = (
}
return displayText.replace(VARIABLE_PATTERN, (match, nodeNameOrId, fieldName) => {
// 特殊处理:表单字段
if (nodeNameOrId === '启动表单' || nodeNameOrId === 'form') {
return `\${form.${fieldName}}`;
}
// 尝试通过名称查找节点
const nodeByName = allNodes.find(n => {
const nodeName = n.data?.label || n.data?.nodeDefinition?.nodeName;
@ -67,6 +72,11 @@ export const convertToDisplayName = (
}
return uuidText.replace(VARIABLE_PATTERN, (match, nodeIdOrName, fieldName) => {
// 特殊处理:表单字段
if (nodeIdOrName === 'form' || nodeIdOrName === '启动表单') {
return `\${.${fieldName}}`;
}
// 尝试通过UUID查找节点
const nodeById = allNodes.find(n => n.id === nodeIdOrName);