236 lines
10 KiB
TypeScript
236 lines
10 KiB
TypeScript
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;
|
||
|