增加GIT代码检测节点

This commit is contained in:
dengqichen 2025-12-05 13:10:19 +08:00
parent ee65e204c6
commit 61ff98737d
6 changed files with 202 additions and 90 deletions

View File

@ -121,14 +121,8 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
const expr = values.expression?.trim(); const expr = values.expression?.trim();
const isDefault = !expr; const isDefault = !expr;
// 1. 检查多个默认分支 // 1. 检查多个默认分支已在UI上显示红色警告框此处直接阻止提交
if (isDefault && hasOtherDefaultBranch) { if (isDefault && hasOtherDefaultBranch) {
toast({
variant: 'destructive',
title: '冲突:多个默认分支',
description: `节点"${sourceNode?.data.label}"已有默认分支。一个节点只能有一个默认分支,请为此分支配置条件表达式。`,
duration: 5000,
});
return; return;
} }
@ -260,20 +254,21 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
</div> </div>
)} )}
{/* 🔴 错误提示:多个默认分支(网关节点) */} {/* 错误提示:多个默认分支(网关节点) */}
{isFromGateway && gatewayType && gatewayType !== 'parallelGateway' && hasOtherDefaultBranch && isDefaultBranch && ( {isFromGateway && gatewayType && gatewayType !== 'parallelGateway' && hasOtherDefaultBranch && isDefaultBranch && (
<div className="rounded-md bg-red-50 dark:bg-red-950/20 border-2 border-red-400 dark:border-red-600 p-4 text-sm mb-6"> <div className="rounded-lg bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-800 p-4 mb-6">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<span className="text-2xl"></span> <div className="flex-shrink-0 w-5 h-5 mt-0.5 rounded-full bg-red-500 flex items-center justify-center">
<div className="flex-1 space-y-2"> <svg className="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<p className="font-bold text-red-900 dark:text-red-200 text-base"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<div className="flex-1">
<p className="font-semibold text-red-900 dark:text-red-200 mb-2">
</p> </p>
<p className="text-red-800 dark:text-red-300"> <p className="text-sm text-red-800 dark:text-red-300 mb-2">
{gatewayType === 'exclusiveGateway' ? '排他' : '包容'}<strong></strong> "表达式"
</p>
<p className="text-red-800 dark:text-red-300 font-medium">
💡 <strong>"表达式"</strong>
</p> </p>
</div> </div>
</div> </div>
@ -281,23 +276,10 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
)} )}
{/* 并行网关:直接显示说明,不允许配置条件 */} {/* 并行网关:简洁提示 */}
{isFromGateway && gatewayType === 'parallelGateway' ? ( {isFromGateway && gatewayType === 'parallelGateway' ? (
<div className="rounded-md bg-green-50 dark:bg-green-950/20 border border-green-200 dark:border-green-800 p-4 text-sm"> <div className="py-8 text-center text-gray-500 dark:text-gray-400">
<div className="flex items-start gap-3"> <p className="text-sm"></p>
<span className="text-2xl"></span>
<div className="flex-1 space-y-2">
<p className="font-medium text-green-900 dark:text-green-200 text-base">
</p>
<p className="text-green-800 dark:text-green-300">
</p>
<p className="text-green-800 dark:text-green-300">
💡 Fork
</p>
</div>
</div>
</div> </div>
) : ( ) : (
<Form {...form}> <Form {...form}>
@ -319,8 +301,8 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
</div> </div>
)} )}
{/* ✅ 错误提示:多个默认分支 */} {/* ✅ 错误提示:多个默认分支(仅非网关节点) */}
{isDefaultBranch && hasOtherDefaultBranch && ( {!isFromGateway && isDefaultBranch && hasOtherDefaultBranch && (
<div className="rounded-md bg-red-50 dark:bg-red-950/20 border-2 border-red-400 dark:border-red-600 p-4 text-sm"> <div className="rounded-md bg-red-50 dark:bg-red-950/20 border-2 border-red-400 dark:border-red-600 p-4 text-sm">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="text-red-600 dark:text-red-400"></span> <span className="text-red-600 dark:text-red-400"></span>
@ -399,9 +381,9 @@ const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
<DialogFooter> <DialogFooter>
<Button type="button" variant="outline" onClick={handleClose}> <Button type="button" variant="outline" onClick={handleClose}>
{isFromGateway && gatewayType === 'parallelGateway' ? '关闭' : '取消'}
</Button> </Button>
{/* 并行网关不需要确定按钮,只需要关闭 */} {/* 并行网关不需要确定按钮 */}
{(!isFromGateway || gatewayType !== 'parallelGateway') && ( {(!isFromGateway || gatewayType !== 'parallelGateway') && (
<Button type="submit" onClick={form.handleSubmit(handleSubmit)}> <Button type="submit" onClick={form.handleSubmit(handleSubmit)}>

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogBody } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogBody } from '@/components/ui/dialog';
import { Keyboard } from 'lucide-react'; import { Keyboard } from 'lucide-react';
interface KeyboardShortcutsPanelProps { interface KeyboardShortcutsPanelProps {
@ -58,6 +58,9 @@ const KeyboardShortcutsPanel: React.FC<KeyboardShortcutsPanelProps> = ({ open, o
<Keyboard className="h-5 w-5" /> <Keyboard className="h-5 w-5" />
</DialogTitle> </DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader> </DialogHeader>
<DialogBody> <DialogBody>

View File

@ -61,7 +61,7 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ options, value = [], onChange
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
variant="outline" variant="outline"

View File

@ -91,10 +91,21 @@ export const useWorkflowSave = () => {
: [] // ✅ 输出能力定义(直接从节点定义中获取) : [] // ✅ 输出能力定义(直接从节点定义中获取)
})), })),
edges: data.edges.map(edge => { edges: data.edges.map(edge => {
// 检查源节点是否为并行网关 // 检查源节点类型
const sourceNode = data.nodes.find(n => n.id === edge.source); const sourceNode = data.nodes.find(n => n.id === edge.source);
const isFromParallelGateway = sourceNode?.data?.nodeType === 'GATEWAY_NODE' const isGateway = sourceNode?.data?.nodeType === 'GATEWAY_NODE';
&& sourceNode?.data?.inputMapping?.gatewayType === 'parallelGateway'; const gatewayType = isGateway ? (sourceNode?.data?.inputMapping?.gatewayType || sourceNode?.data?.configs?.gatewayType) : null;
// 检查是否需要过滤条件配置
const isFromParallelGateway = isGateway && gatewayType === 'parallelGateway';
// 检查是否为汇聚网关多入1出
const incomingEdges = data.edges.filter(e => e.target === sourceNode?.id);
const outgoingEdges = data.edges.filter(e => e.source === sourceNode?.id);
const isConvergingGateway = isGateway && incomingEdges.length >= 2 && outgoingEdges.length === 1;
// 并行网关或汇聚网关不传递条件配置
const shouldRemoveCondition = isFromParallelGateway || isConvergingGateway;
// ⚠️⚠️⚠️ 关键:兜底转换边的表达式(可能从旧数据加载,从未编辑过) // ⚠️⚠️⚠️ 关键:兜底转换边的表达式(可能从旧数据加载,从未编辑过)
const originalCondition = edge.data?.condition; const originalCondition = edge.data?.condition;
@ -121,8 +132,8 @@ export const useWorkflowSave = () => {
targetHandle: edge.targetHandle, // 保存目标连接点 targetHandle: edge.targetHandle, // 保存目标连接点
config: { config: {
type: "sequence", // 固定为sequence类型 type: "sequence", // 固定为sequence类型
// ⚠️ 并行网关的边线不传递条件配置BPMN 规范不允许 // ⚠️ 并行网关或汇聚网关的边线不传递条件配置BPMN 规范)
condition: isFromParallelGateway ? undefined : finalCondition condition: shouldRemoveCondition ? undefined : finalCondition
}, },
// 优先保存多个拐点;若无则回退到单个控制点;否则为空 // 优先保存多个拐点;若无则回退到单个控制点;否则为空
vertices: Array.isArray((edge as any)?.data?.vertices) && (edge as any).data.vertices.length > 0 vertices: Array.isArray((edge as any)?.data?.vertices) && (edge as any).data.vertices.length > 0

View File

@ -435,10 +435,24 @@ const WorkflowDesignInner: React.FC = () => {
const handleEdgeClick = useCallback((event: React.MouseEvent, edge: FlowEdge) => { const handleEdgeClick = useCallback((event: React.MouseEvent, edge: FlowEdge) => {
if (event.detail === 2) { if (event.detail === 2) {
console.log('双击边,打开配置:', edge); console.log('双击边,打开配置:', edge);
// 检查源节点是否为并行网关
const sourceNode = getNodes().find(n => n.id === edge.source);
const gatewayType = (sourceNode?.data?.inputMapping as any)?.gatewayType
|| (sourceNode?.data?.configs as any)?.gatewayType;
const isParallelGateway = sourceNode?.data?.nodeType === 'GATEWAY_NODE'
&& gatewayType === 'parallelGateway';
// 并行网关的边无需配置,直接提示
if (isParallelGateway) {
message.info('并行网关的所有分支自动执行,无需配置条件');
return;
}
setConfigEdge(edge); setConfigEdge(edge);
setEdgeConfigModalVisible(true); setEdgeConfigModalVisible(true);
} }
}, []); }, [getNodes]);
// 处理节点配置更新 // 处理节点配置更新
const handleNodeConfigUpdate = useCallback((nodeId: string, updatedData: Partial<FlowNodeData>) => { const handleNodeConfigUpdate = useCallback((nodeId: string, updatedData: Partial<FlowNodeData>) => {

View File

@ -1,5 +1,19 @@
import type { FlowNode, FlowEdge } from '../types'; import type { FlowNode, FlowEdge } from '../types';
/**
* BPMN 2.0
*/
export enum GatewayMode {
/** 分支模式Diverging1入口 → 多出口 */
DIVERGING = 'diverging',
/** 汇聚模式Converging多入口 → 1出口 */
CONVERGING = 'converging',
/** 混合模式Mixed多入口 → 多出口(不推荐) */
MIXED = 'mixed',
/** 未完成:连线不足或无效 */
INCOMPLETE = 'incomplete'
}
/** /**
* *
*/ */
@ -10,7 +24,36 @@ export interface GatewayValidationResult {
} }
/** /**
* * 线
* @param incomingCount 线
* @param outgoingCount 线
* @returns
*/
export const detectGatewayMode = (
incomingCount: number,
outgoingCount: number
): GatewayMode => {
// 分支模式1入口 + 多出口≥2
if (incomingCount === 1 && outgoingCount >= 2) {
return GatewayMode.DIVERGING;
}
// 汇聚模式多入口≥2+ 1出口
if (incomingCount >= 2 && outgoingCount === 1) {
return GatewayMode.CONVERGING;
}
// 混合模式:多入口 + 多出口(不推荐,视为分支处理)
if (incomingCount >= 2 && outgoingCount >= 2) {
return GatewayMode.MIXED;
}
// 其他情况:未完成或无效
return GatewayMode.INCOMPLETE;
};
/**
*
* @param node * @param node
* @param edges * @param edges
* @returns * @returns
@ -27,69 +70,128 @@ export const validateGatewayNode = (
return { valid: true, errors: [], warnings: [] }; return { valid: true, errors: [], warnings: [] };
} }
const gatewayType = node.data.configs?.gatewayType; // 获取网关类型(优先从 inputMapping兼容 configs
const gatewayType = node.data.inputMapping?.gatewayType || node.data.configs?.gatewayType;
const nodeName = node.data.label || '网关节点'; const nodeName = node.data.label || '网关节点';
// 获取出口连线 // 获取入口和出口连线
const incomingEdges = edges.filter(e => e.target === node.id);
const outgoingEdges = edges.filter(e => e.source === node.id); const outgoingEdges = edges.filter(e => e.source === node.id);
// 1. 检查出口连线数量 // 1. 检测网关模式
if (outgoingEdges.length < 2) { const mode = detectGatewayMode(incomingEdges.length, outgoingEdges.length);
errors.push(`${nodeName}网关节点至少需要2条出口连线当前${outgoingEdges.length}条)`);
return { valid: false, errors, warnings };
}
// 2. 排他网关 / 包容网关特殊验证 // 1.1 特殊情况1入1出透传模式兼容老数据
if (gatewayType === 'exclusiveGateway' || gatewayType === 'inclusiveGateway') { if (incomingEdges.length === 1 && outgoingEdges.length === 1) {
// 检查默认分支 warnings.push(
const defaultBranches = outgoingEdges.filter(e => e.data?.condition?.type === 'DEFAULT'); `${nodeName}网关只有1入1出等同于直接连线建议移除网关节点以简化流程`
if (defaultBranches.length === 0) {
errors.push(`${nodeName}${gatewayType === 'exclusiveGateway' ? '排他' : '包容'}网关必须有一个默认分支`);
} else if (defaultBranches.length > 1) {
errors.push(`${nodeName}:只能有一个默认分支(当前${defaultBranches.length}个)`);
}
// 检查条件分支
const expressionBranches = outgoingEdges.filter(e => e.data?.condition?.type === 'EXPRESSION');
if (gatewayType === 'exclusiveGateway') {
// 排他网关:检查优先级重复
const priorities = expressionBranches.map(e => e.data?.condition?.priority);
const uniquePriorities = new Set(priorities);
if (priorities.length !== uniquePriorities.size) {
errors.push(`${nodeName}:排他网关的分支优先级不能重复`);
}
// 检查是否所有条件分支都有优先级
const noPriorityBranches = expressionBranches.filter(e =>
e.data?.condition?.priority === undefined ||
e.data?.condition?.priority === null
);
if (noPriorityBranches.length > 0) {
errors.push(`${nodeName}:有${noPriorityBranches.length}条分支未设置优先级`);
}
}
// 检查条件表达式是否为空
const emptyExpressionBranches = expressionBranches.filter(e =>
!e.data?.condition?.expression ||
e.data?.condition?.expression.trim() === ''
); );
if (emptyExpressionBranches.length > 0) { // 透传模式不需要条件配置,直接返回通过
warnings.push(`${nodeName}:有${emptyExpressionBranches.length}条分支的条件表达式为空`); return { valid: true, errors, warnings };
}
} }
// 3. 并行网关验证 // 2. 根据模式进行验证
if (gatewayType === 'PARALLEL') { if (mode === GatewayMode.DIVERGING || mode === GatewayMode.MIXED) {
// 并行网关不需要条件,但可以给出提示 // ========== 分支模式验证 ==========
const expressionBranches = outgoingEdges.filter(e => e.data?.condition?.type === 'EXPRESSION'); // 分支网关1入口 + 多出口(混合模式也按分支处理)
if (expressionBranches.length > 0) {
warnings.push(`${nodeName}:并行网关的所有分支都会执行,条件表达式将被忽略`); // 2.1 验证连线数量
if (incomingEdges.length !== 1) {
errors.push(`${nodeName}分支网关需要1条入口连线当前${incomingEdges.length}条)`);
} }
if (outgoingEdges.length < 2) {
errors.push(`${nodeName}分支网关至少需要2条出口连线当前${outgoingEdges.length}条)`);
}
// 如果混合模式,给出警告
if (mode === GatewayMode.MIXED) {
warnings.push(`${nodeName}:检测到混合模式(多入多出),按分支网关处理,建议拆分为多个网关`);
}
// 如果连线数量不足,不再进行条件验证
if (outgoingEdges.length < 2) {
return { valid: false, errors, warnings };
}
// 2.2 排他网关 / 包容网关:验证条件配置
if (gatewayType === 'exclusiveGateway' || gatewayType === 'inclusiveGateway') {
// 检查默认分支
const defaultBranches = outgoingEdges.filter(e => e.data?.condition?.type === 'DEFAULT');
if (defaultBranches.length === 0) {
errors.push(`${nodeName}(分支):${gatewayType === 'exclusiveGateway' ? '排他' : '包容'}网关必须有一个默认分支`);
} else if (defaultBranches.length > 1) {
errors.push(`${nodeName}(分支):只能有一个默认分支(当前${defaultBranches.length}个)`);
}
// 检查条件分支
const expressionBranches = outgoingEdges.filter(e => e.data?.condition?.type === 'EXPRESSION');
if (gatewayType === 'exclusiveGateway') {
// 排他网关:检查优先级重复
const priorities = expressionBranches.map(e => e.data?.condition?.priority);
const uniquePriorities = new Set(priorities);
if (priorities.length !== uniquePriorities.size) {
errors.push(`${nodeName}(分支):排他网关的分支优先级不能重复`);
}
// 检查是否所有条件分支都有优先级
const noPriorityBranches = expressionBranches.filter(e =>
e.data?.condition?.priority === undefined ||
e.data?.condition?.priority === null
);
if (noPriorityBranches.length > 0) {
errors.push(`${nodeName}(分支):有${noPriorityBranches.length}条分支未设置优先级`);
}
}
// 检查条件表达式是否为空
const emptyExpressionBranches = expressionBranches.filter(e =>
!e.data?.condition?.expression ||
e.data?.condition?.expression.trim() === ''
);
if (emptyExpressionBranches.length > 0) {
warnings.push(`${nodeName}(分支):有${emptyExpressionBranches.length}条分支的条件表达式为空`);
}
}
// 2.3 并行网关:不需要条件配置
if (gatewayType === 'parallelGateway') {
const expressionBranches = outgoingEdges.filter(e => e.data?.condition?.type === 'EXPRESSION');
if (expressionBranches.length > 0) {
warnings.push(`${nodeName}(分支):并行网关的所有分支都会执行,条件表达式将被忽略`);
}
}
} else if (mode === GatewayMode.CONVERGING) {
// ========== 汇聚模式验证 ==========
// 汇聚网关:多入口 + 1出口不需要条件配置
// 2.4 验证连线数量
if (incomingEdges.length < 2) {
errors.push(`${nodeName}汇聚网关至少需要2条入口连线当前${incomingEdges.length}条)`);
}
if (outgoingEdges.length !== 1) {
errors.push(`${nodeName}汇聚网关只能有1条出口连线当前${outgoingEdges.length}条)`);
}
// 2.5 汇聚网关不需要条件配置,如果配置了给出警告
const hasConditions = outgoingEdges.some(e =>
e.data?.condition?.type === 'EXPRESSION' ||
e.data?.condition?.type === 'DEFAULT'
);
if (hasConditions) {
warnings.push(`${nodeName}(汇聚网关):汇聚模式不需要条件配置,条件将被忽略`);
}
} else {
// ========== 未完成状态 ==========
errors.push(
`${nodeName}:网关连线配置不完整(当前:${incomingEdges.length}${outgoingEdges.length}出)。` +
`需要配置为分支模式1入≥2出或汇聚模式≥2入1出`
);
} }
// 4. 检查是否所有出口都有连接 // 3. 检查是否所有出口都有连接
const unconnectedEdges = outgoingEdges.filter(e => !e.target); const unconnectedEdges = outgoingEdges.filter(e => !e.target);
if (unconnectedEdges.length > 0) { if (unconnectedEdges.length > 0) {
errors.push(`${nodeName}:有${unconnectedEdges.length}条出口连线未连接到目标节点`); errors.push(`${nodeName}:有${unconnectedEdges.length}条出口连线未连接到目标节点`);