import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { CloseOutlined as CloseOutlinedIcon, PlusOutlined } from "@ant-design/icons"; import { useControllableValue } from "ahooks"; import { Button, Form, Input, Radio, Select, theme } from "antd"; import Show from "@/components/Show"; import type { Expr, ExprComparisonOperator, ExprLogicalOperator, ExprValue, ExprValueSelector, ExprValueType } from "@/domain/workflow"; import { ExprType } from "@/domain/workflow"; import { useAntdFormName, useZustandShallowSelector } from "@/hooks"; import { useWorkflowStore } from "@/stores/workflow"; export type ConditionNodeConfigFormExpressionEditorProps = { className?: string; style?: React.CSSProperties; defaultValue?: Expr; disabled?: boolean; nodeId: string; value?: Expr; onChange?: (value: Expr) => void; }; export type ConditionNodeConfigFormExpressionEditorInstance = { validate: () => Promise; }; // 表单内部使用的扁平结构 type ConditionItem = { // 选择器,格式为 "${nodeId}#${outputName}#${valueType}" // 将 [ExprValueSelector] 转为字符串形式,以便于结构化存储。 leftSelector?: string; // 比较运算符。 operator?: ExprComparisonOperator; // 值。 // 将 [ExprValue] 转为字符串形式,以便于结构化存储。 rightValue?: string; }; type ConditionFormValues = { conditions: ConditionItem[]; logicalOperator: ExprLogicalOperator; }; const initFormModel = (): ConditionFormValues => { return { conditions: [{}], logicalOperator: "and", }; }; const exprToFormValues = (expr?: Expr): ConditionFormValues => { if (!expr) return initFormModel(); const conditions: ConditionItem[] = []; let logicalOp: ExprLogicalOperator = "and"; const extractExpr = (expr: Expr): void => { if (expr.type === ExprType.Comparison) { if (expr.left.type == ExprType.Variant && expr.right.type == ExprType.Constant) { conditions.push({ leftSelector: expr.left.selector?.id != null ? `${expr.left.selector.id}#${expr.left.selector.name}#${expr.left.selector.type}` : undefined, operator: expr.operator != null ? expr.operator : undefined, rightValue: expr.right?.value != null ? String(expr.right.value) : undefined, }); } else { console.warn("[certimate] invalid comparison expression: left must be a variant and right must be a constant", expr); } } else if (expr.type === ExprType.Logical) { logicalOp = expr.operator || "and"; extractExpr(expr.left); extractExpr(expr.right); } }; extractExpr(expr); return { conditions: conditions, logicalOperator: logicalOp, }; }; const formValuesToExpr = (values: ConditionFormValues): Expr | undefined => { const wrapExpr = (condition: ConditionItem): Expr => { const [id, name, type] = (condition.leftSelector?.split("#") ?? ["", "", ""]) as [string, string, ExprValueType]; const valid = !!id && !!name && !!type; const left: Expr = { type: ExprType.Variant, selector: valid ? { id: id, name: name, type: type, } : ({} as ExprValueSelector), }; const right: Expr = { type: ExprType.Constant, value: condition.rightValue!, valueType: type, }; return { type: ExprType.Comparison, operator: condition.operator!, left, right, }; }; if (values.conditions.length === 0) { return undefined; } // 只有一个条件时,直接返回比较表达式 if (values.conditions.length === 1) { const { leftSelector, operator, rightValue } = values.conditions[0]; if (!leftSelector || !operator || !rightValue) { return undefined; } return wrapExpr(values.conditions[0]); } // 多个条件时,通过逻辑运算符连接 let expr: Expr = wrapExpr(values.conditions[0]); for (let i = 1; i < values.conditions.length; i++) { expr = { type: ExprType.Logical, operator: values.logicalOperator, left: expr, right: wrapExpr(values.conditions[i]), }; } return expr; }; const ConditionNodeConfigFormExpressionEditor = forwardRef( ({ className, style, disabled, nodeId, ...props }, ref) => { const { t } = useTranslation(); const { token: themeToken } = theme.useToken(); const { getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"])); const [value, setValue] = useControllableValue(props, { valuePropName: "value", defaultValuePropName: "defaultValue", trigger: "onChange", }); const [formInst] = Form.useForm(); const formName = useAntdFormName({ form: formInst, name: "workflowNodeConditionConfigFormExpressionEditorForm" }); const [formModel, setFormModel] = useState(initFormModel()); useEffect(() => { if (value) { const formValues = exprToFormValues(value); formInst.setFieldsValue(formValues); setFormModel(formValues); } else { formInst.resetFields(); setFormModel(initFormModel()); } }, [value]); const ciSelectorCandidates = useMemo(() => { const previousNodes = getWorkflowOuptutBeforeId(nodeId); return previousNodes .map((node) => { const group = { label: node.name, options: Array<{ label: string; value: string }>(), }; for (const output of node.outputs ?? []) { switch (output.type) { case "certificate": group.options.push({ label: `${output.label} - ${t("workflow.variables.selector.validity.label")}`, value: `${node.id}#${output.name}.validity#boolean`, }); group.options.push({ label: `${output.label} - ${t("workflow.variables.selector.days_left.label")}`, value: `${node.id}#${output.name}.daysLeft#number`, }); break; default: group.options.push({ label: `${output.label}`, value: `${node.id}#${output.name}#${output.type}`, }); console.warn("[certimate] invalid workflow output type in condition expressions", output); break; } } return group; }) .filter((item) => item.options.length > 0); }, [nodeId]); const getValueTypeBySelector = (selector: string): ExprValueType | undefined => { if (!selector) return; const parts = selector.split("#"); if (parts.length >= 3) { return parts[2].toLowerCase() as ExprValueType; } }; const getOperatorsBySelector = (selector: string): { value: ExprComparisonOperator; label: string }[] => { const valueType = getValueTypeBySelector(selector); return getOperatorsByValueType(valueType!); }; const getOperatorsByValueType = (valueType: ExprValue): { value: ExprComparisonOperator; label: string }[] => { switch (valueType) { case "number": return [ { value: "eq", label: t("workflow_node.condition.form.expression.operator.option.eq.label") }, { value: "neq", label: t("workflow_node.condition.form.expression.operator.option.neq.label") }, { value: "gt", label: t("workflow_node.condition.form.expression.operator.option.gt.label") }, { value: "gte", label: t("workflow_node.condition.form.expression.operator.option.gte.label") }, { value: "lt", label: t("workflow_node.condition.form.expression.operator.option.lt.label") }, { value: "lte", label: t("workflow_node.condition.form.expression.operator.option.lte.label") }, ]; case "string": return [ { value: "eq", label: t("workflow_node.condition.form.expression.operator.option.eq.label") }, { value: "neq", label: t("workflow_node.condition.form.expression.operator.option.neq.label") }, ]; case "boolean": return [ { value: "eq", label: t("workflow_node.condition.form.expression.operator.option.eq.alias_is_label") }, { value: "neq", label: t("workflow_node.condition.form.expression.operator.option.neq.alias_not_label") }, ]; default: return []; } }; const handleFormChange = (_: undefined, values: ConditionFormValues) => { setValue(formValuesToExpr(values)); }; useImperativeHandle(ref, () => { return { validate: async () => { await formInst.validateFields(); }, } as ConditionNodeConfigFormExpressionEditorInstance; }); return (
1}> {t("workflow_node.condition.form.expression.logical_operator.option.and.label")} {t("workflow_node.condition.form.expression.logical_operator.option.or.label")} {(fields, { add, remove }) => (
{fields.map(({ key, name: index, ...rest }) => (
{/* 左:变量选择器 */} ); }} {/* 右:输入控件,根据变量类型决定组件 */} { return prevValues.conditions?.[index]?.leftSelector !== currentValues.conditions?.[index]?.leftSelector; }} > {({ getFieldValue }) => { const leftSelector = getFieldValue(["conditions", index, "leftSelector"]); const valueType = getValueTypeBySelector(leftSelector); return ( {valueType === "string" ? ( ) : valueType === "number" ? ( ) : valueType === "boolean" ? ( ) : ( )} ); }}
))}
)}
); } ); export default ConditionNodeConfigFormExpressionEditor;