Просмотр исходного кода

fix: bug#9062、9064、9024、8996

xieyonghong 3 лет назад
Родитель
Сommit
b8f5e704dc

+ 14 - 4
src/components/ProTableCard/CardItems/Scene/index.tsx

@@ -117,10 +117,20 @@ const actionRender = (action: ActionsType) => {
 };
 // 过滤器
 const actionFilter = (terms: any, isLast: boolean, index: number) => {
-  if (isArray(terms)) {
-    return `动作${index + 1}${handleOptionsLabel(terms, isLast ? terms?.[0]?.type : undefined)}`;
-  }
-  return '';
+  let str = `动作${index + 1} `;
+  terms?.forEach((item: any, iindex: number) => {
+    if (isArray(item.terms)) {
+      item.terms.map((iItem: number, iIndex: number) => {
+        str += `${handleOptionsLabel(
+          iItem,
+          iIndex < item.terms.length - 1 ? iItem[3] : undefined,
+        )}`;
+      });
+    }
+    str += iindex < terms.length - 1 ? item.termType : '';
+  });
+
+  return str;
 };
 
 const conditionsRender = (when: any[], index: number) => {

+ 2 - 0
src/pages/rule-engine/Scene/Save/action/DeviceOutput/index.tsx

@@ -106,6 +106,7 @@ export default observer((props: Props) => {
       relationName: DeviceModel.relationName,
       taglist: [],
       columns: [],
+      otherColumns: [],
     };
     _options.name = DeviceModel.deviceDetail?.name;
     const _type = value.message.messageType;
@@ -125,6 +126,7 @@ export default observer((props: Props) => {
       _options.properties = DeviceModel.propertiesName;
       _options.propertiesValue = DeviceModel.propertiesValue;
       _options.columns = DeviceModel.columns;
+      _options.otherColumns = DeviceModel.columns;
     }
     if (_options.selector === 'tag') {
       _options.taglist = DeviceModel.selectorValues?.[0]?.value.map((it: any) => ({

+ 172 - 184
src/pages/rule-engine/Scene/Save/action/ListItem/FilterCondition.tsx

@@ -1,22 +1,21 @@
 import type { TermsType, TermsVale } from '@/pages/rule-engine/Scene/typings';
 import { DropdownButton, ParamsDropdown } from '@/pages/rule-engine/Scene/Save/components/Buttons';
-import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
+import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
 import { useEffect, useState, useCallback, useRef } from 'react';
 import classNames from 'classnames';
 import { observer } from '@formily/react';
-import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
-import '../index.less';
-import { FormModel } from '../..';
 import { Popconfirm, Space } from 'antd';
-import { cloneDeep } from 'lodash';
+import './index.less';
 
 interface FilterProps {
-  thenName: number;
+  branchesName: number;
   branchGroup?: number;
   action?: number;
-  data?: TermsType;
+  data: TermsType;
+  isLast: boolean;
   onChange: (value: TermsType) => void;
-  onLabelChange: (lb: string[]) => void;
+  onLabelChange: (lb: any[]) => void;
+  options: any[];
   label?: any[];
   onAdd: () => void;
   onDelete: () => void;
@@ -25,29 +24,12 @@ interface FilterProps {
 const handleName = (_data: any) => (
   <Space>
     {_data.name}
-    <div style={{ color: 'grey', marginLeft: '5px' }}>{_data.fullName}</div>
+    {/*<div style={{ color: 'grey', marginLeft: '5px' }}>{_data.fullName}</div>*/}
     {_data.description && (
       <div style={{ color: 'grey', marginLeft: '5px' }}>({_data.description})</div>
     )}
   </Space>
 );
-//
-// const handleOptions = (options: any[]): any[] => {
-//   if (!options) return []
-//
-//   return options.map(item => {
-//     const disabled = item.children?.length > 0;
-//     return {
-//       ...item,
-//       column: item.column,
-//       key: item.column,
-//       value: item.column,
-//       title: handleName(item),
-//       disabled: disabled,
-//       children: handleOptions(item.children),
-//     };
-//   })
-// }
 
 const DoubleFilter = ['nbtw', 'btw', 'in', 'nin'];
 const columnOptionsMap = new Map<string, any>();
@@ -61,7 +43,9 @@ export default observer((props: FilterProps) => {
   const [ttOptions, setTtOptions] = useState<any>([]);
   const [valueOptions] = useState<any[]>([]);
   const [valueType, setValueType] = useState('');
-  const labelCache = useRef<any[]>([undefined, undefined, {}]);
+  const labelCache = useRef<any[]>([undefined, undefined, {}, 'and']);
+
+  const [deleteVisible, setDeleteVisible] = useState(false);
 
   const ValueRef = useRef<Partial<TermsType>>({
     column: '',
@@ -120,169 +104,134 @@ export default observer((props: FilterProps) => {
     return [];
   };
 
-  const getParams = useCallback(() => {
-    const _params = {
-      branch: props.thenName,
-      branchGroup: props.branchGroup,
-      action: props.action,
-    };
-    columnOptionsMap.clear();
-    const newData = cloneDeep(FormModel.current);
-    newData.branches = newData.branches?.filter((item) => !!item);
-    queryBuiltInParams(newData, _params).then((res: any) => {
-      if (res.status === 200) {
-        const params = handleTreeData(
-          // res.result.filter((item: any) => !item.id.includes(`action_${props.action}`)),
-          res.result,
-        );
-        setColumnOptions(params);
-        setBuiltInOptions(params);
-        convertLabelValue(props.data?.column);
-      }
-    });
-  }, [props.data?.column]);
-
   useEffect(() => {
     if (props.data) {
       setColumn(props.data.column || '');
       setTermType(props.data.termType || '');
       setValue(props.data.value);
       ValueRef.current = props.data || {};
+      handleTreeData(props.options || []);
       convertLabelValue(props.data.column);
     }
   }, [props.data]);
 
   useEffect(() => {
-    if (props.data) {
-      getParams();
-    }
-  }, []);
+    const newOptions = handleTreeData(props.options || []);
+    convertLabelValue(props.data?.column);
+    setBuiltInOptions(newOptions);
+    setColumnOptions(newOptions);
+  }, [props.options]);
 
   useEffect(() => {
-    labelCache.current = props.label || [undefined, undefined, {}];
+    labelCache.current = props.label || [undefined, undefined, {}, 'and'];
   }, [props.label]);
 
   return (
     <div className="filter-condition-warp">
-      {props.data ? (
-        <div className="filter-condition-content">
-          <Popconfirm title={'确认删除?'} onConfirm={props.onDelete}>
-            <div className={classNames('filter-condition-delete danger show')}>
-              <DeleteOutlined />
-            </div>
-          </Popconfirm>
-          <DropdownButton
-            options={columnOptions}
-            type="param"
-            placeholder="请选择参数"
-            value={column}
-            showLabelKey="fullName"
-            isTree={true}
-            onChange={(_value, item) => {
-              setValue({
+      <div
+        className="filter-condition-content"
+        onMouseOver={() => {
+          setDeleteVisible(true);
+        }}
+        onMouseOut={() => {
+          setDeleteVisible(false);
+        }}
+      >
+        <Popconfirm title={'确认删除?'} onConfirm={props.onDelete}>
+          <div className={classNames('filter-condition-delete', { show: deleteVisible })}>
+            <CloseOutlined />
+          </div>
+        </Popconfirm>
+        <DropdownButton
+          options={columnOptions}
+          type="param"
+          placeholder="请选择参数"
+          value={column}
+          showLabelKey="fullName"
+          isTree={true}
+          onChange={(_value, item) => {
+            setValue({
+              value: undefined,
+              source: 'fixed',
+            });
+            // paramChange(item);
+            setColumn(_value!);
+            const node = item.node;
+            const _termTypeOptions: any[] =
+              node.termTypes?.map((tItem: any) => ({ title: tItem.name, key: tItem.id })) || [];
+            setTtOptions(_termTypeOptions);
+            // 默认选中第一个
+            let _termTypeValue = undefined;
+            if (_termTypeOptions.length) {
+              _termTypeValue = _termTypeOptions[0].key;
+              labelCache.current[1] = _termTypeValue;
+              setTermType(_termTypeValue);
+            } else {
+              labelCache.current[1] = '';
+              setTermType('');
+            }
+            ValueRef.current.column = _value!;
+            labelCache.current[0] = node.fullName;
+            valueChange({
+              column: _value,
+              value: {
                 value: undefined,
                 source: 'fixed',
-              });
-              // paramChange(item);
-              setColumn(_value!);
-              const node = item.node;
-              const _termTypeOptions: any[] =
-                node.termTypes?.map((tItem: any) => ({ title: tItem.name, key: tItem.id })) || [];
-              setTtOptions(_termTypeOptions);
-              // 默认选中第一个
-              let _termTypeValue = undefined;
-              if (_termTypeOptions.length) {
-                _termTypeValue = _termTypeOptions[0].key;
-                labelCache.current[1] = _termTypeValue;
-                setTermType(_termTypeValue);
-              } else {
-                labelCache.current[1] = '';
-                setTermType('');
-              }
-              ValueRef.current.column = _value!;
-              labelCache.current[0] = node.fullName;
-              valueChange({
-                column: _value,
-                value: {
-                  value: undefined,
-                  source: 'fixed',
-                },
-                termType: _termTypeValue,
-              });
-            }}
-          />
-          <DropdownButton
-            options={ttOptions}
-            type="termType"
-            placeholder="操作符"
-            value={termType}
-            onChange={(v) => {
-              const _value = {
-                ...value,
-              };
-              if (value && DoubleFilter.includes(v!)) {
-                _value.value = [undefined, undefined];
-              } else {
-                _value.value = undefined;
-              }
-              setValue(_value);
-              setTermType(v!);
+              },
+              termType: _termTypeValue,
+            });
+          }}
+        />
+        <DropdownButton
+          options={ttOptions}
+          type="termType"
+          placeholder="操作符"
+          value={termType}
+          onChange={(v) => {
+            const _value = {
+              ...value,
+            };
+            if (value && DoubleFilter.includes(v!)) {
+              _value.value = [undefined, undefined];
+            } else {
+              _value.value = undefined;
+            }
+            setValue(_value);
+            setTermType(v!);
 
-              labelCache.current[1] = v;
-              ValueRef.current.termType = v;
-              valueChange({
-                column: props.data!.column,
-                value: _value as TermsVale,
-                termType: v,
-              });
-            }}
-          />
-          {DoubleFilter.includes(termType) ? (
-            <>
-              <ParamsDropdown
-                options={valueOptions}
-                type="value"
-                placeholder="参数值"
-                valueType={valueType}
-                value={value}
-                BuiltInOptions={BuiltInOptions}
-                showLabelKey="fullName"
-                name={0}
-                onChange={(v, lb) => {
-                  const _myValue = {
-                    value: [v.value, ValueRef.current.value?.value?.[1]],
-                    source: v.source,
-                  };
-                  ValueRef.current.value = _myValue;
-                  setValue(_myValue);
-                  labelCache.current[2] = { ...labelCache.current[2], 0: lb };
-                  props.onLabelChange?.(labelCache.current);
-                  valueEventChange(_myValue);
-                }}
-              />
-              <ParamsDropdown
-                options={valueOptions}
-                type="value"
-                placeholder="参数值"
-                valueType={valueType}
-                value={value}
-                BuiltInOptions={BuiltInOptions}
-                showLabelKey="fullName"
-                name={1}
-                onChange={(v, lb) => {
-                  const _myValue = {
-                    value: [ValueRef.current.value?.value?.[0], v.value],
-                    source: v.source,
-                  };
-                  ValueRef.current.value = _myValue;
-                  setValue(_myValue);
-                  labelCache.current[2] = { ...labelCache.current[2], 1: lb };
-                  props.onLabelChange?.(labelCache.current);
-                  valueEventChange(_myValue);
-                }}
-              />
-            </>
-          ) : (
+            labelCache.current[1] = v;
+            ValueRef.current.termType = v;
+            valueChange({
+              column: props.data!.column,
+              value: _value as TermsVale,
+              termType: v,
+            });
+          }}
+        />
+        {DoubleFilter.includes(termType) ? (
+          <>
+            <ParamsDropdown
+              options={valueOptions}
+              type="value"
+              placeholder="参数值"
+              valueType={valueType}
+              value={value}
+              BuiltInOptions={BuiltInOptions}
+              showLabelKey="fullName"
+              name={0}
+              onChange={(v, lb) => {
+                const _myValue = {
+                  value: [v.value, ValueRef.current.value?.value?.[1]],
+                  source: v.source,
+                };
+                ValueRef.current.value = _myValue;
+                setValue(_myValue);
+                labelCache.current[2] = { ...labelCache.current[2], 0: lb };
+                labelCache.current[3] = props.data.type;
+                props.onLabelChange?.(labelCache.current);
+                valueEventChange(_myValue);
+              }}
+            />
             <ParamsDropdown
               options={valueOptions}
               type="value"
@@ -291,26 +240,65 @@ export default observer((props: FilterProps) => {
               value={value}
               BuiltInOptions={BuiltInOptions}
               showLabelKey="fullName"
+              name={1}
               onChange={(v, lb) => {
-                setValue({
-                  ...v,
-                });
-                labelCache.current[2] = { 0: lb };
+                const _myValue = {
+                  value: [ValueRef.current.value?.value?.[0], v.value],
+                  source: v.source,
+                };
+                ValueRef.current.value = _myValue;
+                setValue(_myValue);
+                labelCache.current[2] = { ...labelCache.current[2], 1: lb };
+                labelCache.current[3] = props.data.type;
                 props.onLabelChange?.(labelCache.current);
-                valueEventChange(v);
+                valueEventChange(_myValue);
               }}
             />
-          )}
+          </>
+        ) : (
+          <ParamsDropdown
+            options={valueOptions}
+            type="value"
+            placeholder="参数值"
+            valueType={valueType}
+            value={value}
+            BuiltInOptions={BuiltInOptions}
+            showLabelKey="fullName"
+            onChange={(v, lb) => {
+              setValue({
+                ...v,
+              });
+              labelCache.current[2] = { 0: lb };
+              labelCache.current[3] = props.data.type;
+              props.onLabelChange?.(labelCache.current);
+              valueEventChange(v);
+            }}
+          />
+        )}
+      </div>
+      {!props.isLast ? (
+        <div className="term-type-warp">
+          <DropdownButton
+            options={[
+              { title: '并且', key: 'and' },
+              { title: '或者', key: 'or' },
+            ]}
+            isTree={false}
+            type="type"
+            value={props.data.type}
+            onChange={(v) => {
+              props.data.type = v;
+              labelCache.current[3] = v;
+              props.onLabelChange?.([...labelCache.current]);
+            }}
+          />
         </div>
       ) : (
-        <div
-          className="filter-add-button"
-          onClick={() => {
-            props.onAdd();
-            getParams();
-          }}
-        >
-          <PlusOutlined style={{ paddingRight: 16 }} /> 添加过滤条件
+        <div className="terms-filter-add" onClick={props.onAdd}>
+          <div className="terms-filter-content">
+            <PlusOutlined style={{ fontSize: 12, paddingRight: 4 }} />
+            <span>条件</span>
+          </div>
         </div>
       )}
     </div>

+ 183 - 0
src/pages/rule-engine/Scene/Save/action/ListItem/FilterGroup.tsx

@@ -0,0 +1,183 @@
+import { observer, Observer } from '@formily/react';
+import ParamsItem from './FilterCondition';
+import { useEffect, useRef, useState } from 'react';
+import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
+import { DropdownButton } from '@/pages/rule-engine/Scene/Save/components/Buttons';
+import classNames from 'classnames';
+import type { TermsType } from '@/pages/rule-engine/Scene/typings';
+import { isArray } from 'lodash';
+import './index.less';
+import { Form, Popconfirm } from 'antd';
+
+interface TermsProps {
+  data: TermsType;
+  name: number;
+  action: number;
+  branchesName: number;
+  branchGroup: number;
+  isLast: boolean;
+  label?: any;
+  paramsOptions: any[];
+  actionColumns: any[];
+  onValueChange: (data: TermsType) => void;
+  onLabelChange: (label: any) => void;
+  onDelete: () => void;
+  onAddGroup: () => void;
+  onColumnsChange: (columns: any[]) => void;
+}
+
+export default observer((props: TermsProps) => {
+  const [deleteVisible, setDeleteVisible] = useState(false);
+  const _name = [props.branchesName, 'then', props.branchGroup, 'actions', props.action, 'terms'];
+  const labelRef = useRef<any>({});
+
+  useEffect(() => {
+    labelRef.current = props.label || {};
+  }, [props.label]);
+
+  return (
+    <div className="terms-params">
+      <div className="terms-params-warp">
+        <div
+          className="terms-params-content"
+          onMouseOver={() => {
+            setDeleteVisible(true);
+          }}
+          onMouseOut={() => {
+            setDeleteVisible(false);
+          }}
+        >
+          <Observer>
+            {() => {
+              const terms = props.data.terms || [];
+              return terms.map((item, index) => (
+                <Form.Item
+                  name={['branches', ..._name, props.name, 'terms', index]}
+                  rules={[
+                    {
+                      validator(_, v) {
+                        if (v) {
+                          if (!v.column) {
+                            return Promise.reject(new Error('请选择参数'));
+                          }
+
+                          if (!v.termType) {
+                            return Promise.reject(new Error('请选择操作符'));
+                          }
+
+                          if (!v.value) {
+                            return Promise.reject(new Error('请选择或输入参数值'));
+                          } else {
+                            if (isArray(v.value.value) && v.value.value.some((_v: any) => !_v)) {
+                              return Promise.reject(new Error('请选择或输入参数值'));
+                            } else if (!v.value.value) {
+                              return Promise.reject(new Error('请选择或输入参数值'));
+                            }
+                          }
+                        } else {
+                          return Promise.reject(new Error('请选择参数'));
+                        }
+                        return Promise.resolve();
+                      },
+                    },
+                  ]}
+                >
+                  <ParamsItem
+                    branchesName={props.branchesName}
+                    branchGroup={props.branchGroup}
+                    action={props.action}
+                    data={item}
+                    key={item.key}
+                    options={props.paramsOptions}
+                    label={labelRef.current?.terms?.[index]}
+                    isLast={index === props.data.terms!.length - 1}
+                    onDelete={() => {
+                      terms.splice(index, 1);
+                      if (terms.length === 0) {
+                        props.onDelete();
+                      } else {
+                        props.onValueChange({
+                          ...props.data,
+                          terms: terms,
+                        });
+                      }
+                    }}
+                    onAdd={() => {
+                      const key = `params_${new Date().getTime()}`;
+                      terms.push({
+                        type: 'and',
+                        column: undefined,
+                        value: undefined,
+                        termType: undefined,
+                        key,
+                      });
+                      props.onValueChange({
+                        ...props.data,
+                        terms: terms,
+                      });
+                    }}
+                    onChange={(data) => {
+                      terms[index] = {
+                        ...terms[index],
+                        ...data,
+                      };
+
+                      props.onValueChange({
+                        ...props.data,
+                        terms: terms,
+                      });
+                    }}
+                    onLabelChange={(options) => {
+                      let newLabel: any = [];
+                      const typeLabel = props.data.type === 'and' ? '并且' : '或者';
+                      if (labelRef.current?.terms) {
+                        labelRef.current?.terms.splice(index, 1, options);
+                        newLabel = labelRef.current;
+                      } else {
+                        newLabel = {
+                          terms: [options],
+                          termType: typeLabel,
+                        };
+                      }
+
+                      labelRef.current = newLabel;
+                      props.onLabelChange(newLabel);
+                    }}
+                  />
+                </Form.Item>
+              ));
+            }}
+          </Observer>
+          <Popconfirm title={'确认删除?'} onConfirm={props.onDelete}>
+            <div className={classNames('terms-params-delete', { show: deleteVisible })}>
+              <CloseOutlined />
+            </div>
+          </Popconfirm>
+        </div>
+        {!props.isLast ? (
+          <div className="term-type-warp">
+            <DropdownButton
+              options={[
+                { title: '并且', key: 'and' },
+                { title: '或者', key: 'or' },
+              ]}
+              isTree={false}
+              type="type"
+              value={props.data.type}
+              onChange={(v) => {
+                props.data.type = v;
+              }}
+            />
+          </div>
+        ) : (
+          <div className="terms-add" onClick={props.onAddGroup}>
+            <div className="terms-content">
+              <PlusOutlined style={{ fontSize: 12, paddingRight: 4 }} />
+              <span>分组</span>
+            </div>
+          </div>
+        )}
+      </div>
+    </div>
+  );
+});

+ 187 - 85
src/pages/rule-engine/Scene/Save/action/ListItem/Item.tsx

@@ -1,13 +1,17 @@
-import { useEffect, useState, useRef } from 'react';
+import { useEffect, useState, useRef, useCallback } from 'react';
 import Modal, { ActionTypeComponent } from '../Modal/add';
 import type { ActionsType } from '@/pages/rule-engine/Scene/typings';
-import { DeleteOutlined } from '@ant-design/icons';
+import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
 import './index.less';
 import TriggerAlarm from '../TriggerAlarm';
 import { AddButton } from '@/pages/rule-engine/Scene/Save/components/Buttons';
-import FilterCondition from './FilterCondition';
-import { isArray, set } from 'lodash';
-import { Form, Popconfirm } from 'antd';
+import FilterGroup from './FilterGroup';
+import { cloneDeep, set } from 'lodash';
+import { Popconfirm, Space } from 'antd';
+import { TermsType } from '@/pages/rule-engine/Scene/typings';
+import { FormModel } from '@/pages/rule-engine/Scene/Save';
+import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
+import { randomString } from '@/utils/util';
 
 export enum ParallelEnum {
   'parallel' = 'parallel',
@@ -17,8 +21,8 @@ export enum ParallelEnum {
 export type ParallelType = keyof typeof ParallelEnum;
 
 interface ItemProps {
-  thenName: number;
-  branchGroup?: number;
+  branchesName: number;
+  branchGroup: number;
   name: number;
   data: ActionsType;
   type: ParallelType;
@@ -43,17 +47,68 @@ itemNotifyIconMap.set('voice', require('/public/images/scene/notify-item-img/voi
 itemNotifyIconMap.set('sms', require('/public/images/scene/notify-item-img/sms.png'));
 itemNotifyIconMap.set('webhook', require('/public/images/scene/notify-item-img/webhook.png'));
 
+const handleName = (_data: any) => (
+  <Space>
+    {_data.name}
+    <div style={{ color: 'grey', marginLeft: '5px' }}>{_data.fullName}</div>
+    {_data.description && (
+      <div style={{ color: 'grey', marginLeft: '5px' }}>({_data.description})</div>
+    )}
+  </Space>
+);
+
 export default (props: ItemProps) => {
   const [visible, setVisible] = useState<boolean>(false);
   const [triggerVisible, setTriggerVisible] = useState<boolean>(false);
-  const [op, setOp] = useState<any>(props.options);
+  // const [op, setOp] = useState<any>(props.options);
   const cacheValueRef = useRef<any>({});
   const [actionType, setActionType] = useState<string>('');
+  const [thenTerms, setThenTerms] = useState<TermsType[] | undefined>([]);
+  const [paramsOptions, setParamsOptions] = useState<any[]>([]);
+
+  const optionsRef = useRef<any[]>([]);
 
   useEffect(() => {
-    setOp(props.options);
+    // setOp(props.options);
+    optionsRef.current = props.options;
   }, [props.options]);
 
+  const handleTreeData = (data: any): any[] => {
+    if (data.length > 0) {
+      return data.map((item: any) => {
+        const name = handleName(item);
+        if (item.children) {
+          return {
+            ...item,
+            key: item.id,
+            fullName: item.name,
+            title: name,
+            disabled: true,
+            children: handleTreeData(item.children),
+          };
+        }
+        return { ...item, key: item.id, fullName: item.name, title: name };
+      });
+    }
+    return [];
+  };
+
+  const getParams = useCallback(() => {
+    const _params = {
+      branch: props.branchesName,
+      branchGroup: props.branchGroup,
+      action: props.name,
+    };
+    const newData = cloneDeep(FormModel.current);
+    newData.branches = newData.branches?.filter((item) => !!item);
+    queryBuiltInParams(newData, _params).then((res: any) => {
+      if (res.status === 200) {
+        const params = handleTreeData(res.result);
+        setParamsOptions(params);
+      }
+    });
+  }, [props.name, props.branchesName, props.branchGroup]);
+
   const notifyRender = (data: ActionsType | undefined, options: any) => {
     switch (data?.notify?.notifyType) {
       case 'dingTalk':
@@ -277,8 +332,13 @@ export default (props: ItemProps) => {
 
   useEffect(() => {
     cacheValueRef.current = props.data;
+    setThenTerms(props.data.terms);
   }, [props.data]);
 
+  useEffect(() => {
+    getParams();
+  }, []);
+
   return (
     <div className="actions-item-warp">
       <div className="actions-item">
@@ -305,98 +365,139 @@ export default (props: ItemProps) => {
           </div>
         </Popconfirm>
       </div>
-      {props.parallel ? null : (
-        <Form.Item
-          name={[
-            'branches',
-            props.thenName,
-            'then',
-            props.branchGroup || 0,
-            'actions',
-            props.name,
-            'terms',
-          ]}
-          rules={[
-            {
-              validator(_, v) {
-                if (v) {
-                  if (!v.column) {
-                    return Promise.reject(new Error('请选择参数'));
-                  }
-
-                  if (!v.termType) {
-                    return Promise.reject(new Error('请选择操作符'));
-                  }
-
-                  if (!v.value) {
-                    return Promise.reject(new Error('请选择或输入参数值'));
-                  } else {
-                    if (isArray(v.value.value) && v.value.value.some((_v: any) => !_v)) {
-                      return Promise.reject(new Error('请选择或输入参数值'));
-                    } else if (!v.value.value) {
-                      return Promise.reject(new Error('请选择或输入参数值'));
-                    }
-                  }
-                }
-                return Promise.resolve();
-              },
-            },
-          ]}
-        >
-          <FilterCondition
-            action={props.name}
-            branchGroup={props.branchGroup}
-            thenName={props.thenName}
-            data={props.data.terms?.[0]}
-            label={props.data.options?.terms}
-            onAdd={() => {
+      <div className={'actions-item-filter-warp'}>
+        {props.parallel ? null : thenTerms && thenTerms.length ? (
+          thenTerms.map((termsItem, index) => (
+            <FilterGroup
+              action={props.name}
+              key={termsItem.key}
+              branchGroup={props.branchGroup}
+              branchesName={props.branchesName}
+              name={index}
+              data={termsItem}
+              isLast={index === thenTerms.length - 1}
+              paramsOptions={paramsOptions}
+              label={props.options?.terms?.[index]}
+              actionColumns={props.options?.otherColumns}
+              onColumnsChange={(columns) => {
+                optionsRef.current['columns'] = columns;
+                props.onUpdate(props.data, optionsRef.current);
+              }}
+              onAddGroup={() => {
+                const newThenTerms = [...thenTerms];
+                newThenTerms.push({
+                  type: 'and',
+                  key: randomString(),
+                  terms: [
+                    {
+                      column: undefined,
+                      value: undefined,
+                      termType: undefined,
+                      type: 'and',
+                      key: randomString(),
+                    },
+                  ],
+                });
+                const _data = props.data;
+                set(_data, 'terms', newThenTerms);
+                props.onUpdate(_data, optionsRef.current);
+              }}
+              onValueChange={(termsData) => {
+                const _data = props.data;
+                console.log('update-onValueChange', termsData, optionsRef.current);
+                set(_data, ['terms', index], termsData);
+                // cacheValueRef.current = _data;
+                props.onUpdate(_data, {
+                  ...optionsRef.current,
+                });
+              }}
+              onLabelChange={(lb) => {
+                const newLabel: any[] = props.options?.terms || [];
+                newLabel.splice(index, 1, lb);
+                optionsRef.current['terms'] = newLabel;
+                props.onUpdate(props.data, optionsRef.current);
+              }}
+              onDelete={() => {
+                const _data = thenTerms.filter((a) => a.key !== termsItem.key);
+                console.log(_data, thenTerms, termsItem);
+                props.onUpdate(
+                  {
+                    ...props.data,
+                    terms: _data,
+                  },
+                  optionsRef.current,
+                );
+              }}
+            />
+          ))
+        ) : (
+          <div
+            className="filter-add-button"
+            onClick={() => {
+              getParams();
               let _data = props.data;
+              optionsRef.current['terms'] = [];
               if (!_data.terms) {
                 _data = {
                   ..._data,
-                  terms: [{}],
+                  terms: [
+                    {
+                      type: 'and',
+                      key: randomString(),
+                      terms: [
+                        {
+                          column: undefined,
+                          value: undefined,
+                          termType: undefined,
+                          type: 'and',
+                          key: randomString(),
+                        },
+                      ],
+                    },
+                  ],
                 };
-                cacheValueRef.current = _data;
-                props.onUpdate(_data, op);
-              }
-            }}
-            onChange={(termsData) => {
-              const _data = props.data;
-              set(_data, 'terms', [termsData]);
-              cacheValueRef.current = _data;
-              props.onUpdate(_data, {
-                ...op,
-              });
-            }}
-            onLabelChange={(lb) => {
-              props.onUpdate(cacheValueRef.current, {
-                ...op,
-                terms: lb,
-              });
-            }}
-            onDelete={() => {
-              const _data = props.data;
-              if (_data.terms) {
-                delete _data.terms;
-                props.onUpdate(_data, op);
+                props.onUpdate(_data, optionsRef.current);
+              } else {
+                _data.terms = [
+                  {
+                    type: 'and',
+                    key: randomString(),
+                    terms: [
+                      {
+                        column: undefined,
+                        value: undefined,
+                        termType: undefined,
+                        type: 'and',
+                        key: randomString(),
+                      },
+                    ],
+                  },
+                ];
+                props.onUpdate(_data, optionsRef.current);
               }
             }}
-          />
-        </Form.Item>
-      )}
+          >
+            <PlusOutlined style={{ paddingRight: 16 }} /> 添加过滤条件
+          </div>
+        )}
+      </div>
       {visible && (
         <Modal
           name={props.name}
           branchGroup={props.branchGroup}
-          thenName={props.thenName}
+          branchesName={props.branchesName}
           data={props.data}
           close={() => {
             setVisible(false);
           }}
           save={(data: ActionsType, options) => {
-            setOp(options);
+            // setOp(options);
+            optionsRef.current = options;
             props.onUpdate(data, options);
             setVisible(false);
+            setTimeout(() => {
+              getParams();
+            }, 10);
           }}
           parallel={props.parallel}
         />
@@ -411,14 +512,15 @@ export default (props: ItemProps) => {
       <ActionTypeComponent
         name={props.name}
         branchGroup={props.branchGroup}
-        thenName={props.thenName}
+        branchesName={props.branchesName}
         data={props.data}
         type={actionType}
         close={() => {
           setActionType('');
         }}
         save={(data: ActionsType, options) => {
-          setOp(options);
+          // setOp(options);
+          optionsRef.current = options;
           props.onUpdate(data, options);
           setActionType('');
         }}

+ 33 - 25
src/pages/rule-engine/Scene/Save/action/ListItem/List.tsx

@@ -5,8 +5,10 @@ import './index.less';
 import type { ActionsType } from '@/pages/rule-engine/Scene/typings';
 import Item from './Item';
 import type { ParallelType } from './Item';
+import { Observer } from '@formily/react';
+
 interface ListProps {
-  thenName: number;
+  branchesName: number;
   type: ParallelType;
   actions: ActionsType[];
   parallel: boolean;
@@ -24,29 +26,35 @@ export default (props: ListProps) => {
 
   return (
     <div className="action-list-content">
-      {actions.map((item, index) => (
-        <Item
-          thenName={props.thenName}
-          branchGroup={props.parallel ? 1 : 0}
-          name={index}
-          data={item}
-          type={props.type}
-          key={item.key}
-          parallel={props.parallel}
-          options={item.options}
-          onDelete={() => {
-            props.onDelete(item.key!);
-          }}
-          onUpdate={(data, options) => {
-            props.onAdd({
-              ...item,
-              ...data,
-              options,
-            });
-            setVisible(false);
-          }}
-        />
-      ))}
+      <Observer>
+        {() => {
+          return actions.map((item, index) => (
+            <Item
+              branchesName={props.branchesName}
+              branchGroup={props.parallel ? 1 : 0}
+              name={index}
+              data={item}
+              type={props.type}
+              key={item.key}
+              parallel={props.parallel}
+              options={item.options}
+              onDelete={() => {
+                props.onDelete(item.key!);
+              }}
+              onUpdate={(data, options) => {
+                props.onAdd({
+                  ...item,
+                  ...data,
+                  options,
+                });
+                console.log('update-options', options);
+                setVisible(false);
+              }}
+            />
+          ));
+        }}
+      </Observer>
+
       <AddButton
         onClick={() => {
           setVisible(true);
@@ -60,7 +68,7 @@ export default (props: ListProps) => {
           parallel={props.parallel}
           name={props.actions.length}
           branchGroup={props.parallel ? 1 : 0}
-          thenName={props.thenName}
+          branchesName={props.branchesName}
           data={{
             key: `${props.type}_${props.actions.length}`,
           }}

+ 93 - 15
src/pages/rule-engine/Scene/Save/action/ListItem/index.less

@@ -10,7 +10,15 @@
       border-top: 1px solid @primary-color;
     }
   }
+
+  .filter-add-button {
+    display: inline-block;
+    margin-bottom: 24px;
+    color: #2f54eb;
+    cursor: pointer;
+  }
 }
+
 .actions-item {
   position: relative;
   margin-bottom: 24px;
@@ -38,6 +46,7 @@
       padding: 0 8px;
       background: #fafafa;
       cursor: pointer;
+
       div {
         padding: 6px 10px;
         color: #333;
@@ -45,10 +54,12 @@
         line-height: 22px;
         background-color: #fff;
         border-radius: 22px;
+
         .notify-text-highlight {
           margin-left: 5px;
           font-weight: bold;
         }
+
         .notify-img-highlight {
           margin: 0 10px;
           color: rgba(0, 0, 0, 0.8);
@@ -87,34 +98,101 @@
 }
 
 .filter-condition-warp {
+  display: flex;
+  align-items: center;
+
   .filter-condition-content {
     position: relative;
-    padding: 16px;
+    display: flex;
+    padding: 4px;
     border: 1px dashed rgba(0, 0, 0, 0.3);
-    row-gap: 16px;
+    border-radius: 2px;
+    transition: border 0.3s;
 
     .filter-condition-delete {
       position: absolute;
-      top: 0;
-      right: 0;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      width: 32px;
-      height: 32px;
-      color: #e50012;
-      background-color: rgba(229, 0, 18, 0.1);
+      top: -10px;
+      right: -10px;
+      display: none;
+      width: 20px;
+      height: 20px;
+      color: #999;
+      line-height: 20px;
+      text-align: center;
+      background-color: #f1f1f1;
       border-radius: 50%;
-      transform: translate(50%, -50%);
       cursor: pointer;
 
+      &.show {
+        display: block;
+      }
+
       &:hover {
-        background-color: rgba(229, 0, 18, 0.2);
+        background-color: #f3f3f3;
       }
     }
   }
-  .filter-add-button {
-    color: #2f54eb;
+
+  .terms-filter-add {
+    // display: inline-block;
+    width: 66px;
+    margin-left: 16px;
+    padding: 4px 8px;
+    color: #333;
+    background: #e8e8e8;
+    border: 1px solid #f0f0f0;
+    border-radius: 30px;
     cursor: pointer;
   }
 }
+
+.actions-item-filter-warp {
+  display: flex;
+  padding-top: 10px;
+  overflow-x: auto;
+  overflow-y: visible;
+  row-gap: 16px;
+
+  .terms-params {
+    // display: inline-block;
+    display: flex;
+    flex-shrink: 0;
+    margin-bottom: 24px;
+
+    // &:not(:first-child) {
+    //   margin-bottom: 16px;
+    // }
+
+    .terms-params-warp {
+      display: flex;
+      align-items: center;
+    }
+
+    .terms-params-content {
+      position: relative;
+      display: flex;
+      // flex-wrap: wrap;
+      padding: 8px;
+      padding-bottom: 0;
+      background-color: #fafafa;
+      row-gap: 16px;
+
+      .terms-params-item {
+        display: flex;
+        align-items: center;
+      }
+    }
+
+    .term-type-warp {
+      // display: inline-block;
+      width: 50px;
+      margin: 0 16px;
+
+      .term-type {
+        padding-top: 4px;
+        padding-bottom: 4px;
+        border-radius: 2px;
+      }
+    }
+  }
+}

+ 3 - 3
src/pages/rule-engine/Scene/Save/action/Modal/add.tsx

@@ -13,7 +13,7 @@ interface ActionTypeProps {
   data: Partial<ActionsType>;
   close: () => void;
   parallel: boolean;
-  thenName: number;
+  branchesName: number;
   branchGroup?: number;
 }
 
@@ -39,7 +39,7 @@ export const ActionTypeComponent = (props: ActionTypeProps) => {
           }}
           name={props.name}
           branchGroup={props.branchGroup}
-          thenName={props.thenName}
+          thenName={props.branchesName}
           cancel={() => {
             props.close();
           }}
@@ -99,7 +99,7 @@ interface Props {
   save: (data: any, options?: any) => void;
   data: Partial<ActionsType>;
   name: number;
-  thenName: number;
+  branchesName: number;
   branchGroup?: number;
   // type: ParallelType;
   parallel: boolean;

+ 2 - 2
src/pages/rule-engine/Scene/Save/action/index.tsx

@@ -85,7 +85,7 @@ export default (props: ActionsProps) => {
           >
             <div className="actions-list">
               <List
-                thenName={props.name}
+                branchesName={props.name}
                 type="serial"
                 parallel={false}
                 actions={serialArray.length ? serialArray[0].actions : []}
@@ -134,7 +134,7 @@ export default (props: ActionsProps) => {
           >
             <div className="actions-list">
               <List
-                thenName={props.name}
+                branchesName={props.name}
                 type="parallel"
                 parallel={true}
                 actions={parallelArray.length ? parallelArray[0].actions : []}

+ 6 - 1
src/pages/rule-engine/Scene/Save/components/Buttons/Dropdown.tsx

@@ -22,6 +22,7 @@ interface DropdownButtonProps {
   type: 'param' | 'termType' | 'value' | 'type';
   fieldNames?: any;
   showLabelKey?: string;
+  onVailChange?: (vail: boolean) => void;
 }
 
 const TypeStyle = {
@@ -45,9 +46,14 @@ const DropdownButton = (props: DropdownButtonProps) => {
     (key?: string) => {
       if (key && paramOptions.length) {
         const labelOptions = valueOptions.get(key);
+        console.log('dropdown', key, labelOptions);
         if (labelOptions) {
           const nameKey = props.showLabelKey || 'title';
           setLabel(labelOptions[nameKey]);
+          props.onVailChange?.(true);
+        } else {
+          setLabel(key);
+          props.onVailChange?.(false);
         }
       } else {
         setLabel(key!);
@@ -78,7 +84,6 @@ const DropdownButton = (props: DropdownButtonProps) => {
     if (props.showLabelKey) {
       titleKey = props.showLabelKey;
     }
-    console.log('drop-tree', e.node, titleKey);
     setLabel(e.node[titleKey]);
   };
 

+ 1 - 0
src/pages/rule-engine/Scene/Save/index.less

@@ -10,6 +10,7 @@
   display: flex;
   align-items: center;
   justify-content: flex-start;
+  margin-bottom: 16px;
 
   .scene-header-type {
     display: flex;

+ 1 - 1
src/pages/rule-engine/Scene/Save/index.tsx

@@ -158,7 +158,7 @@ export default observer(() => {
       case 'device':
         return <Device form={_form} />;
       case 'manual':
-        return <Manual form={_form} />;
+        return <Manual />;
       case 'timer':
         return <Timer form={_form} />;
       default:

+ 6 - 2
src/pages/rule-engine/Scene/Save/terms/branchItem.tsx

@@ -9,6 +9,7 @@ import classNames from 'classnames';
 import { set } from 'lodash';
 import { Store } from 'jetlinks-store';
 import { Form, FormInstance, Popconfirm } from 'antd';
+import { BranchesThen } from '@/pages/rule-engine/Scene/typings';
 
 interface BranchesItemProps {
   name: number;
@@ -23,12 +24,12 @@ interface BranchesItemProps {
 
 export default observer((props: BranchesItemProps) => {
   const [when, setWhen] = useState<TermsType[]>([]);
+  const [actions, setActions] = useState<BranchesThen[]>([]);
   const [error, setError] = useState(false);
   const [deleteVisible, setDeleteVisible] = useState(false);
 
   useEffect(() => {
     Store.subscribe('TriggerDeviceModel', (data) => {
-      console.log('Store', data);
       if (data.update) {
         setError(true);
       }
@@ -39,6 +40,9 @@ export default observer((props: BranchesItemProps) => {
     if (props.data.when) {
       setWhen(props.data.when);
     }
+    if (props.data.then) {
+      setActions(props.data.then);
+    }
   }, [props.data]);
 
   const addWhen = (index: number) => {
@@ -158,7 +162,7 @@ export default observer((props: BranchesItemProps) => {
                   <Actions
                     openShakeLimit={true}
                     name={props.name}
-                    thenOptions={props.data.then}
+                    thenOptions={actions}
                     onAdd={(data) => {
                       if (FormModel.current.branches && data) {
                         const newThen = [...FormModel.current.branches[props.name].then, data];

+ 42 - 32
src/pages/rule-engine/Scene/Save/terms/paramsItem.tsx

@@ -30,7 +30,7 @@ interface ParamsItemProps {
 const handleName = (_data: any) => (
   <Space>
     {_data.name}
-    <div style={{ color: 'grey', marginLeft: '5px' }}>{_data.fullName}</div>
+    {/*<div style={{ color: 'grey', marginLeft: '5px' }}>{_data.fullName}</div>*/}
     {_data.description && (
       <div style={{ color: 'grey', marginLeft: '5px' }}>({_data.description})</div>
     )}
@@ -58,37 +58,41 @@ const DoubleFilter = ['nbtw', 'btw', 'in', 'nin'];
 
 export const handleOptionsLabel = (data: any, type?: string) => {
   if (isArray(data)) {
-    const c = data[0];
-    const t = data[1];
-    const v = data[2];
-    const termsTypeKey = {
-      eq: '等于_value',
-      neq: '不等于_value',
-      gt: '大于_value',
-      gte: '大于等于_value',
-      lt: '小于_value',
-      lte: '小于等于_value',
-      btw: '在_value和_value2之间',
-      nbtw: '不在_value和_value2之间',
-      time_gt_now: '距离当前时间大于_value秒',
-      time_lt_now: '距离当前时间小于_value秒',
-      in: '在_value,_value2之中',
-      nin: '不在_value,_value2之中',
-      like: '包含_value',
-      nlike: '不包含_value',
-    };
-    const typeKey = {
-      and: '并且',
-      or: '或者',
-    };
-    const _value = isObject(v) ? Object.values(v) : [v];
-    const typeStr = type ? typeKey[type] : '';
-    if (DoubleFilter.includes(t)) {
-      const str = termsTypeKey[t].replace('_value', _value[0]).replace('_value2', _value[1]);
-      return `${c} ${str} ${typeStr}`;
+    try {
+      const c = data[0];
+      const t = data[1];
+      const v = data[2];
+      const termsTypeKey = {
+        eq: '等于_value',
+        neq: '不等于_value',
+        gt: '大于_value',
+        gte: '大于等于_value',
+        lt: '小于_value',
+        lte: '小于等于_value',
+        btw: '在_value和_value2之间',
+        nbtw: '不在_value和_value2之间',
+        time_gt_now: '距离当前时间大于_value秒',
+        time_lt_now: '距离当前时间小于_value秒',
+        in: '在_value,_value2之中',
+        nin: '不在_value,_value2之中',
+        like: '包含_value',
+        nlike: '不包含_value',
+      };
+      const typeKey = {
+        and: '并且',
+        or: '或者',
+      };
+      const _value = isObject(v) ? Object.values(v) : [v];
+      const typeStr = type ? typeKey[type] : '';
+      if (DoubleFilter.includes(t)) {
+        const str = termsTypeKey[t].replace('_value', _value[0]).replace('_value2', _value[1]);
+        return `${c} ${str} ${typeStr} `;
+      }
+      const str = termsTypeKey[t].replace('_value', _value[0]);
+      return `${c} ${str} ${typeStr} `;
+    } catch (e) {
+      return data;
     }
-    const str = termsTypeKey[t].replace('_value', _value[0]);
-    return `${c} ${str} ${typeStr}`;
   }
   return data;
 };
@@ -138,8 +142,8 @@ const ParamsItem = observer((props: ParamsItemProps) => {
   const convertLabelValue = useCallback(
     (columnValue?: string) => {
       if (columnValue && paramOptions.length) {
-        console.log('paramsValueOptions ===>>', paramsValueOptions, columnValue);
         const labelOptions = paramsValueOptions.get(columnValue);
+        console.log('paramsValueOptions ===>>', columnValue, labelOptions);
         if (labelOptions) {
           const _termTypeOptions: any[] =
             labelOptions?.termTypes?.map((tItem: any) => ({ title: tItem.name, key: tItem.id })) ||
@@ -154,6 +158,9 @@ const ParamsItem = observer((props: ParamsItemProps) => {
             }));
             setMetricsOptions(_metrics);
           }
+        } else {
+          setTtOptions([]);
+          setValueType('');
         }
       }
     },
@@ -203,6 +210,9 @@ const ParamsItem = observer((props: ParamsItemProps) => {
           }}
           showLabelKey="fullName"
           isTree={true}
+          onVailChange={(v) => {
+            console.log('onVailChange', v);
+          }}
           onChange={(_value, item) => {
             setValue({
               value: undefined,

+ 4 - 1
src/pages/rule-engine/Scene/Save/terms/term.tsx

@@ -67,11 +67,14 @@ export default observer((props: TermsProps) => {
               const terms: TermsType[] = _when?.terms || [];
               return terms.map((item, index) => (
                 <Form.Item
-                  name={['branches', ...props.pName, props.name, 'terms', index]}
+                  name={['branches', ...props.pName, props.whenName, 'terms', props.name]}
                   rules={[
                     {
                       validator(_, v) {
                         if (v) {
+                          if (!Object.keys(v).length) {
+                            return Promise.reject(new Error('该数据已发生变更,请重新配置'));
+                          }
                           if (!v.column) {
                             return Promise.reject(new Error('请选择参数'));
                           }