deploy-ease-platform/frontend/src/pages/Workflow/Design/components/EdgeConfigModal.tsx
dengqichen fd0b615e6b 1
2025-10-21 13:18:50 +08:00

236 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { useToast } from '@/components/ui/use-toast';
import type { FlowEdge } from '../types';
interface EdgeConfigModalProps {
visible: boolean;
edge: FlowEdge | null;
onOk: (edgeId: string, condition: EdgeCondition) => void;
onCancel: () => void;
}
export interface EdgeCondition {
type: 'EXPRESSION' | 'DEFAULT';
expression?: string;
priority: number;
}
// Zod 表单验证 Schema
const edgeConditionSchema = z.object({
type: z.enum(['EXPRESSION', 'DEFAULT'], {
required_error: '请选择条件类型',
}),
expression: z.string().optional(),
priority: z.number()
.min(1, '优先级最小为 1')
.max(999, '优先级最大为 999'),
}).refine((data) => {
// 如果是表达式类型expression 必填
if (data.type === 'EXPRESSION') {
return data.expression && data.expression.trim().length > 0;
}
return true;
}, {
message: '请输入条件表达式',
path: ['expression'],
});
type EdgeConditionFormValues = z.infer<typeof edgeConditionSchema>;
/**
* 边条件配置弹窗
* 使用 shadcn/ui Dialog + react-hook-form
*/
const EdgeConfigModal: React.FC<EdgeConfigModalProps> = ({
visible,
edge,
onOk,
onCancel
}) => {
const { toast } = useToast();
const [conditionType, setConditionType] = useState<'EXPRESSION' | 'DEFAULT'>('EXPRESSION');
const form = useForm<EdgeConditionFormValues>({
resolver: zodResolver(edgeConditionSchema),
defaultValues: {
type: 'EXPRESSION',
expression: '',
priority: 10,
},
});
// 当 edge 变化时,更新表单值
useEffect(() => {
if (visible && edge) {
const condition = edge.data?.condition;
const values = {
type: (condition?.type || 'EXPRESSION') as 'EXPRESSION' | 'DEFAULT',
expression: condition?.expression || '',
priority: condition?.priority || 10,
};
form.reset(values);
setConditionType(values.type);
}
}, [visible, edge, form]);
const handleSubmit = (values: EdgeConditionFormValues) => {
if (!edge) return;
// 检查表达式是否包含变量引用
if (values.type === 'EXPRESSION' && values.expression) {
const hasVariable = /\$\{[\w.]+\}/.test(values.expression);
if (!hasVariable) {
toast({
title: '提示',
description: '表达式建议包含变量引用,格式:${变量名}',
});
}
}
onOk(edge.id, values);
handleClose();
};
const handleClose = () => {
form.reset();
setConditionType('EXPRESSION');
onCancel();
};
return (
<Dialog open={visible} onOpenChange={(open) => !open && handleClose()}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel></FormLabel>
<FormControl>
<div className="flex gap-4">
<div className="flex items-center space-x-2">
<input
type="radio"
id="type-expression"
value="EXPRESSION"
checked={field.value === 'EXPRESSION'}
onChange={(e) => {
field.onChange(e.target.value);
setConditionType('EXPRESSION');
}}
className="h-4 w-4"
/>
<Label htmlFor="type-expression" className="cursor-pointer font-normal">
</Label>
</div>
<div className="flex items-center space-x-2">
<input
type="radio"
id="type-default"
value="DEFAULT"
checked={field.value === 'DEFAULT'}
onChange={(e) => {
field.onChange(e.target.value);
setConditionType('DEFAULT');
}}
className="h-4 w-4"
/>
<Label htmlFor="type-default" className="cursor-pointer font-normal">
</Label>
</div>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{conditionType === 'EXPRESSION' ? (
<FormField
control={form.control}
name="expression"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Textarea
placeholder="请输入条件表达式,如:${amount} > 1000"
rows={4}
{...field}
/>
</FormControl>
<FormDescription>
使 {'${变量名}'} {'${amount} > 1000'}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
) : (
<div className="rounded-md bg-muted p-3 text-sm text-muted-foreground">
</div>
)}
<FormField
control={form.control}
name="priority"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
type="number"
min={1}
max={999}
placeholder="请输入优先级"
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormDescription>
1-999
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type="button" variant="outline" onClick={handleClose}>
</Button>
<Button type="submit">
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
export default EdgeConfigModal;