feat(web): if-else node show cases

This commit is contained in:
zhaoying
2026-03-24 15:13:50 +08:00
parent 9cbffd6408
commit 611b14dfea
4 changed files with 175 additions and 37 deletions

View File

@@ -1,26 +1,49 @@
import { useRef } from 'react'
import { useTranslation } from 'react-i18next'
import clsx from 'clsx';
import type { ReactShapeConfig } from '@antv/x6-react-shape';
import { Flex } from 'antd';
import NodeTools from './NodeTools'
import { useVariableList } from '../Properties/hooks/useVariableList'
const caculateIsSet = (item: any, type: string) => {
switch(type) {
switch (type) {
case 'categories':
return typeof item?.class_name === 'string' && item?.class_name !== ''
case 'cases':
return item.expressions.length > 0 && item.expressions.filter((vo: any) => {
const keys = Object.keys(vo)
return keys.length === 0 || (keys.length > 0
&& ((['not_empty', 'empty'].includes(vo.operator) && (['undefined', 'null'].includes(typeof vo.left) || vo.left === ''))
|| (!['not_empty', 'empty'].includes(vo.operator) && (['undefined', 'null'].includes(typeof vo.right) || vo.right === ''))))
}).length === 0
case 'cases': {
if (!item.left) return false
if (['not_empty', 'empty'].includes(item.operator)) return true
return !!item.left && (!!item.right || typeof item.right === 'boolean')
}
}
}
const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
const data = node?.getData() || {};
const { t } = useTranslation()
const graphRef = useRef(node?.model?.graph)
const variableList = useVariableList(node ?? null, graphRef, [])
const getLocaleField = (field: string, filedType: string) => {
const key = filedType === 'boolean' ? `workflow.config.if-else..boolean.${field}` : filedType === 'number' ? `workflow.config.if-else.num.${field}` : `workflow.config.if-else.${field}`
const value = t(key)
return value !== key ? value : t(`workflow.config.if-else.num.${field}`)
};
const labelRender = (value: string) => {
const filterOption = variableList.find(vo => `{{${vo.value}}}` === value)
if (filterOption) {
return (
<span
className="rb:max-w-[40%] rb:break-all rb:line-clamp-1 rb:text-[#155EEF]"
contentEditable={false}
>
{`{x}`} {filterOption.label}
</span>
)
}
return null
}
return (
<div className={clsx('rb:cursor-pointer rb:group rb:relative rb:h-full rb:w-full rb:p-3 rb:border rb:rounded-2xl rb:bg-[#FCFCFD] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)]', {
@@ -48,16 +71,33 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
{data.type === 'if-else' &&
<Flex vertical gap={4} className="rb:mt-3!">
{data.config?.cases?.defaultValue.map((item: any, index: number) => (
<div key={index} className="rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:py-1 rb:px-1.5 rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
<Flex justify="space-between">
<span>{index === 0 ? 'IF' : `ELIF`}</span>
{caculateIsSet(item, 'cases') ? t(`workflow.config.${data.type}.set`) : t(`workflow.config.${data.type}.unset`)}
<div key={index} className={item.expressions.length > 0 ? '' : 'rb:mb-1'}>
<Flex justify={item.expressions.length > 0 ? "space-between" : 'end'} className="rb:mb-1">
{item.expressions.length > 0 && <span className="rb:text-[#5B6167] rb:text-[10px]">CASE{index + 1}</span>}
<span className="rb:text-[#212332] rb:font-medium rb:text-[12px]">{index === 0 ? 'IF' : `ELIF`}</span>
</Flex>
{item.expressions.length > 0 && <Flex vertical gap={2}>
{item.expressions.map((expression: any, eIndex: number) => (
<div key={eIndex} className="rb:relative">
{item.expressions.length > 1 && eIndex > 0 && <div className="rb:absolute rb:-top-2 rb:right-2 rb:text-[10px] rb:text-[#155EEF] rb:font-medium rb:leading-3.5 rb:text-right rb:pr-0.5">{item.logical_operator.toLocaleUpperCase()}</div>}
<Flex align="center" className="rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:py-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
{caculateIsSet(expression, 'cases')
? <>
{labelRender(expression.left)}
<span className="rb:mx-1">{getLocaleField(expression.operator, typeof expression.right)}</span>
<span className="rb:break-all rb:line-clamp-1">{!['not_empty', 'empty'].includes(expression.operator) && <span>{typeof expression.right === 'boolean' ? String(expression.right).charAt(0).toUpperCase() + String(expression.right).slice(1) : expression.right}</span>}</span>
</>
: t(`workflow.config.${data.type}.unset`)
}
</Flex>
</div>
))}
</Flex>}
</div>
))}
<div className="rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:py-1 rb:px-1.5 rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
<Flex justify="end" className="rb:text-[#212332] rb:font-medium rb:text-[12px]">
ELSE
</div>
</Flex>
</Flex>
}
</div>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-09 18:24:53
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-06 14:24:57
* @Last Modified time: 2026-03-24 15:00:46
*/
import { type FC } from 'react'
import clsx from 'clsx'
@@ -12,9 +12,10 @@ import { Form, Button, Select, Space, Divider, InputNumber, type SelectProps, Fl
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import VariableSelect from '../VariableSelect'
import Editor from '../../Editor'
import { edgeAttrs, conditionNodeItemHeight, nodeWidth, portItemArgsY, conditionNodePortItemArgsY, conditionNodeHeight } from '../../../constant'
import { edgeAttrs, nodeWidth } from '../../../constant'
import RbButton from '@/components/RbButton';
import RadioGroupBtn from '../RadioGroupBtn'
import { calcConditionNodeTotalHeight, getConditionNodeCasePortY } from '../../../utils'
interface CaseListProps {
value?: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; input_type?: string; }[] }>;
@@ -60,6 +61,16 @@ const CaseList: FC<CaseListProps> = ({
const { t } = useTranslation();
const form = Form.useFormInstance();
// Recalculate node height and port Y positions without rebuilding ports
const updateNodeLayout = (cases: any[]) => {
if (!selectedNode || !graphRef?.current) return;
selectedNode.prop('size', { width: nodeWidth, height: calcConditionNodeTotalHeight(cases) });
cases.forEach((_c: any, i: number) => {
selectedNode.portProp(`CASE${i + 1}`, 'args/y', getConditionNodeCasePortY(cases, i));
});
selectedNode.portProp(`CASE${cases.length + 1}`, 'args/y', getConditionNodeCasePortY(cases, cases.length));
};
// Update node ports based on case count changes (add/remove cases)
const updateNodePorts = (caseCount: number, removedCaseIndex?: number) => {
if (!selectedNode || !graphRef?.current) return;
@@ -89,19 +100,10 @@ const CaseList: FC<CaseListProps> = ({
selectedNode.removePort(port.id);
}
});
// Calculate new node height: base height 88px + 30px for each additional port
const totalPorts = caseCount + 1; // IF/ELIF + ELSE
const newHeight = conditionNodeHeight + (totalPorts - 2) * conditionNodeItemHeight;
selectedNode.prop('size', { width: nodeWidth, height: newHeight })
const cases = form.getFieldValue(name) || [];
selectedNode.prop('size', { width: nodeWidth, height: calcConditionNodeTotalHeight(cases) });
// Update right port x position
currentPorts.forEach((port: any) => {
if (port.group === 'right' && port.args) {
selectedNode.portProp(port.id!, 'args/x', nodeWidth);
}
});
// Add ELIF ports
for (let i = 0; i < caseCount; i++) {
selectedNode.addPort({
@@ -109,7 +111,7 @@ const CaseList: FC<CaseListProps> = ({
group: 'right',
args: {
x: nodeWidth,
y: portItemArgsY * i + conditionNodePortItemArgsY,
y: getConditionNodeCasePortY(cases, i),
},
});
}
@@ -120,7 +122,7 @@ const CaseList: FC<CaseListProps> = ({
group: 'right',
args: {
x: nodeWidth,
y: portItemArgsY * caseCount + conditionNodePortItemArgsY,
y: getConditionNodeCasePortY(cases, caseCount),
},
});
@@ -351,7 +353,10 @@ const CaseList: FC<CaseListProps> = ({
</div>
<div
className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/deleteBg.svg')] rb:hover:bg-[url('@/assets/images/workflow/deleteBg_hover.svg')]"
onClick={() => removeCondition(conditionField.name)}
onClick={() => {
removeCondition(conditionField.name);
setTimeout(() => updateNodeLayout(form.getFieldValue(name) || []), 100);
}}
></div>
</Flex>
)
@@ -360,14 +365,17 @@ const CaseList: FC<CaseListProps> = ({
<Row>
<Col flex="1">
<Button
onClick={() => addCondition({})}
onClick={() => {
addCondition({});
setTimeout(() => updateNodeLayout(form.getFieldValue(name) || []), 100);
}}
className={clsx("rb:py-0! rb:px-1! rb:h-4.5! rb:rounded-sm! rb:text-[12px]!")}
size="small"
>
+ {t('workflow.config.addCase')}
</Button>
</Col>
<Col flex="70px">
{caseFields.length > 1 && <Col flex="70px">
<RbButton
danger
className="rb:group rb:mr-5 rb:py-0! rb:px-1! rb:h-4.5! rb:rounded-sm! rb:text-[12px]! rb:gap-0!"
@@ -376,7 +384,7 @@ const CaseList: FC<CaseListProps> = ({
>
{t('common.remove')}
</RbButton>
</Col>
</Col>}
</Row>
</Col>
</Row>