Wzyyy98 vor 3 Jahren
Ursprung
Commit
79f9bfbe41
38 geänderte Dateien mit 1220 neuen und 338 gelöschten Zeilen
  1. BIN
      public/images/scene/action-bind-icon.png
  2. BIN
      public/images/scene/action-delay-icon.png
  3. BIN
      public/images/scene/action-device-icon.png
  4. BIN
      public/images/scene/action-notify-icon.png
  5. BIN
      public/images/scene/action-unbind-icon.png
  6. 6 0
      src/components/ProTableCard/CardItems/Scene/MyTooltip/index.tsx
  7. 99 40
      src/components/ProTableCard/CardItems/Scene/index.tsx
  8. 83 0
      src/pages/rule-engine/Scene/Save/action/Delay/index.tsx
  9. 42 49
      src/pages/rule-engine/Scene/Save/action/ListItem/Item.tsx
  10. 29 7
      src/pages/rule-engine/Scene/Save/action/ListItem/List.tsx
  11. 3 2
      src/pages/rule-engine/Scene/Save/action/ListItem/index.less
  12. 24 2
      src/pages/rule-engine/Scene/Save/action/Modal/add.tsx
  13. 74 17
      src/pages/rule-engine/Scene/Save/action/index.tsx
  14. 7 3
      src/pages/rule-engine/Scene/Save/action/notify/NotifyConfig.tsx
  15. 12 5
      src/pages/rule-engine/Scene/Save/action/notify/NotifyTemplate.tsx
  16. 2 2
      src/pages/rule-engine/Scene/Save/action/notify/index.tsx
  17. 40 11
      src/pages/rule-engine/Scene/Save/components/Buttons/Dropdown.tsx
  18. 216 27
      src/pages/rule-engine/Scene/Save/components/Buttons/ParamsDropdown.tsx
  19. 20 0
      src/pages/rule-engine/Scene/Save/components/Buttons/index.less
  20. 1 1
      src/pages/rule-engine/Scene/Save/components/ParamsSelect/index.less
  21. 27 12
      src/pages/rule-engine/Scene/Save/components/ParamsSelect/index.tsx
  22. 1 1
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/whenOption.tsx
  23. 23 19
      src/pages/rule-engine/Scene/Save/components/TriggerWay/actionsType.tsx
  24. 15 12
      src/pages/rule-engine/Scene/Save/device/deviceList.tsx
  25. 10 3
      src/pages/rule-engine/Scene/Save/device/index.tsx
  26. 4 4
      src/pages/rule-engine/Scene/Save/device/type.tsx
  27. 3 0
      src/pages/rule-engine/Scene/Save/index.less
  28. 30 5
      src/pages/rule-engine/Scene/Save/index.tsx
  29. 11 4
      src/pages/rule-engine/Scene/Save/manual/index.tsx
  30. 51 38
      src/pages/rule-engine/Scene/Save/terms/branchItem.tsx
  31. 1 1
      src/pages/rule-engine/Scene/Save/terms/index.less
  32. 33 3
      src/pages/rule-engine/Scene/Save/terms/index.tsx
  33. 261 37
      src/pages/rule-engine/Scene/Save/terms/paramsItem.tsx
  34. 65 25
      src/pages/rule-engine/Scene/Save/terms/term.tsx
  35. 15 4
      src/pages/rule-engine/Scene/Save/timer/index.tsx
  36. 1 1
      src/pages/rule-engine/Scene/Save/trigger/index.tsx
  37. 2 1
      src/pages/rule-engine/Scene/index.tsx
  38. 9 2
      src/pages/rule-engine/Scene/typings.d.ts

BIN
public/images/scene/action-bind-icon.png


BIN
public/images/scene/action-delay-icon.png


BIN
public/images/scene/action-device-icon.png


BIN
public/images/scene/action-notify-icon.png


BIN
public/images/scene/action-unbind-icon.png


+ 6 - 0
src/components/ProTableCard/CardItems/Scene/MyTooltip/index.tsx

@@ -0,0 +1,6 @@
+import { TooltipProps } from 'antd/lib/tooltip';
+import { Tooltip } from 'antd';
+
+export default (props: TooltipProps) => {
+  return <Tooltip {...props} overlayInnerStyle={{ color: 'black' }} color={'white'} />;
+};

+ 99 - 40
src/components/ProTableCard/CardItems/Scene/index.tsx

@@ -8,6 +8,7 @@ import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
 import { CheckOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
 import classNames from 'classnames';
 import { ActionsType, BranchesThen } from '@/pages/rule-engine/Scene/typings';
+import MyTooltip from './MyTooltip';
 
 const imageMap = new Map();
 imageMap.set('timer', require('/public/images/scene/scene-timer.png'));
@@ -75,6 +76,19 @@ const selectorRender = (obj: any) => {
   }
 };
 
+const selectorContextRender = (obj: any) => {
+  switch (obj?.selector) {
+    case 'all':
+      return `所有的${obj?.productId}`;
+    case 'fixed':
+      return `设备${(obj?.selectorValues || '').map((item: any) => item?.name).join(',')}`;
+    case 'org':
+      return `部门${(obj?.selectorValues || '').map((item: any) => item?.name).join(',')}`;
+    default:
+      return '';
+  }
+};
+
 const timerRender = (timer: any) => {
   if (timer?.trigger && timer?.mod) {
     const trigger = timer?.trigger;
@@ -182,16 +196,27 @@ const notifyRender = (data: ActionsType | undefined) => {
 };
 
 const deviceRender = (data: ActionsType | undefined) => {
-  console.log(data);
   switch (data?.device?.selector) {
     case 'relation':
       return (
         <div>
-          {data?.options?.type || ''}
-          与【触发设备】具有相同【关系名称】的【产品名称】设备的【属性/功能】
+          {data?.options?.type || ''}与
+          <span className={styles['notify-text-highlight']}>{data?.options?.deviceName || ''}</span>
+          具有相同
+          <span className={styles['notify-text-highlight']}>
+            {data?.options?.relationName || ''}
+          </span>
+          的
+          <span className={styles['notify-text-highlight']}>
+            {data?.options?.productName || ''}
+          </span>
+          设备的
+          <span className={styles['notify-text-highlight']}>
+            {data?.options?.propertyName || data?.options?.functionName || ''}
+          </span>
         </div>
       );
-    case 'tag': //`【读取/设置/执行】【标签名称】为【标签值】【并且/或者】【标签名称】为【标签值】的【产品名称】【属性/功能】`
+    case 'tag':
       return (
         <div>
           {data?.options?.type || ''}
@@ -208,11 +233,12 @@ const deviceRender = (data: ActionsType | undefined) => {
     default:
       return (
         <div>
-          {data?.options?.type || ''}
-          <span className={styles['notify-text-highlight']}>{data?.options?.deviceName || ''}</span>
-          <span className={styles['notify-text-highlight']}>
-            {data?.options?.propertyName || data?.options?.functionName || ''}
-          </span>
+          {/*{data?.options?.type || ''}*/}
+          {/*<span className={styles['notify-text-highlight']}>{data?.options?.deviceName || ''}</span>*/}
+          {/*<span className={styles['notify-text-highlight']}>*/}
+          {/*  {data?.options?.propertyName || data?.options?.functionName || ''}*/}
+          {/*</span>*/}
+          打开空调动作1执行结果=成功并且 湿度24
         </div>
       );
   }
@@ -275,7 +301,7 @@ const actionRender = (action: ActionsType, index: number) => {
 
 const conditionsRender = (when: any[], index: number) => {
   if (when.length) {
-    return when[index];
+    return (when[index]?.terms || []).join('');
   }
   return '';
 };
@@ -284,8 +310,10 @@ const branchesActionRender = (actions: any[]) => {
   if (actions && actions?.length) {
     const list: any[] = [];
     actions.map((item, index) => {
-      list.push(actionRender(item, index));
+      const dt = actionRender(item, index);
+      list.push(dt);
     });
+
     return list.map((item, index) => (
       <div className={styles['right-item-right-item-contents-item']}>
         <div style={{ minWidth: 40 }}>动作{index + 1}</div>
@@ -295,6 +323,7 @@ const branchesActionRender = (actions: any[]) => {
   }
   return '';
 };
+
 const ContentRender = (data: SceneCardProps) => {
   const [visible, setVisible] = useState<boolean>(false);
   const type = data.triggerType;
@@ -307,17 +336,24 @@ const ContentRender = (data: SceneCardProps) => {
     const operation: any = data.trigger.device?.operation;
     return (
       <div className={styles['card-item-content-box']}>
-        <div className={styles['card-item-content-trigger']}>
-          {selectorRender(obj)}
-          {operation ? (
-            <span>
-              {timerRender(operation?.timer)}
-              {operatorRender(operation)}
-            </span>
-          ) : (
-            ''
-          )}
-        </div>
+        <MyTooltip
+          placement="topLeft"
+          title={`${
+            selectorContextRender(obj) + timerRender(operation?.timer) + operatorRender(operation)
+          }`}
+        >
+          <div className={classNames(styles['card-item-content-trigger'], 'ellipsis')}>
+            {selectorRender(obj)}
+            {operation ? (
+              <span>
+                {timerRender(operation?.timer)}
+                {operatorRender(operation)}
+              </span>
+            ) : (
+              ''
+            )}
+          </div>
+        </MyTooltip>
         <div className={styles['card-item-content-action']}>
           {(visible ? data.branches || [] : (data?.branches || []).slice(0, 2)).map(
             (item: any, index) => {
@@ -329,14 +365,21 @@ const ContentRender = (data: SceneCardProps) => {
                   <div className={styles['card-item-content-action-item-right']}>
                     <div className={styles['card-item-content-action-item-right-item']}>
                       <div className={styles['right-item-left']}>
-                        <div className={classNames(styles['trigger-conditions'], 'ellipsis')}>
-                          {conditionsRender(data.options?.terms || [], index)}
-                        </div>
-                        {item.shakeLimit?.enabled && (
-                          <div className={classNames(styles['trigger-shake'], 'ellipsis')}>
-                            ({item.shakeLimit?.time}秒内发生{item.shakeLimit?.threshold}
-                            次以上时执行一次)
+                        <MyTooltip title={conditionsRender(data.options?.terms || [], index)}>
+                          <div className={classNames(styles['trigger-conditions'], 'ellipsis')}>
+                            {conditionsRender(data.options?.terms || [], index)}
                           </div>
+                        </MyTooltip>
+                        {item.shakeLimit?.enabled && (
+                          <MyTooltip
+                            title={`(${item.shakeLimit?.time}秒内发生${item.shakeLimit?.threshold}
+                            次以上时执行一次)`}
+                          >
+                            <div className={classNames(styles['trigger-shake'], 'ellipsis')}>
+                              ({item.shakeLimit?.time}秒内发生{item.shakeLimit?.threshold}
+                              次以上时执行一次)
+                            </div>
+                          </MyTooltip>
                         )}
                       </div>
                       <div className={styles['right-item-right']}>
@@ -371,20 +414,36 @@ const ContentRender = (data: SceneCardProps) => {
         </div>
       </div>
     );
-  } else if (type !== 'device' && data.actions?.length) {
+  } else if (type !== 'device' && data.branches?.length) {
     return (
       <div className={styles['card-item-content-box']}>
         <div className={styles['card-item-content-action']}>
-          {data.actions?.length && (
-            <div className={styles['card-item-content-action-item']}>
-              <div className={styles['card-item-content-action-item-left']}>执行</div>
-              <div className={styles['card-item-content-action-item-right']}>
-                {(data?.actions || []).map((item: any, index) => {
-                  return actionRender(item, index);
-                })}
-              </div>
-            </div>
-          )}
+          {data.branches?.length &&
+            (data?.branches || []).map((item: any, index) => {
+              return (
+                <div className={styles['card-item-content-action-item']} key={item?.key || index}>
+                  <div className={styles['card-item-content-action-item-left']}>执行</div>
+                  <div className={styles['card-item-content-action-item-right']}>
+                    <div className={styles['card-item-content-action-item-right-item']}>
+                      <div className={styles['right-item-right']}>
+                        {(item?.then || []).map((i: BranchesThen, _index: number) => {
+                          return (
+                            <div key={i?.key || _index} className={styles['right-item-right-item']}>
+                              <div className={styles['trigger-ways']}>
+                                {i.parallel ? '并行执行' : '串行执行'}
+                              </div>
+                              <div className={classNames(styles['right-item-right-item-contents'])}>
+                                {branchesActionRender(Array.isArray(i?.actions) ? i?.actions : [])}
+                              </div>
+                            </div>
+                          );
+                        })}
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              );
+            })}
         </div>
       </div>
     );

+ 83 - 0
src/pages/rule-engine/Scene/Save/action/Delay/index.tsx

@@ -0,0 +1,83 @@
+import { Modal, Select, InputNumber } from 'antd';
+import { useEffect, useState } from 'react';
+import { observer } from '@formily/react';
+
+export enum TimeUnit {
+  'seconds' = 'seconds',
+  'minutes' = 'minutes',
+  'hours' = 'hours',
+}
+
+export const timeUnitEnum = {
+  seconds: '秒',
+  minutes: '分',
+  hours: '小时',
+};
+
+interface Props {
+  value: {
+    time?: number;
+    unit?: string;
+  };
+  save: (
+    notify: {
+      time?: number;
+      unit?: string;
+    },
+    options: any,
+  ) => void;
+  cancel: () => void;
+}
+
+export default observer((props: Props) => {
+  const [value, setValue] = useState<number>(0);
+  const [unit, setUnit] = useState('seconds');
+  useEffect(() => {
+    if (props.value) {
+      setUnit(props.value.unit || 'seconds');
+      setValue(props.value.time || 0);
+    }
+  }, []);
+
+  const TimeTypeAfter = (
+    <Select
+      options={[
+        { label: '秒', value: 'seconds' },
+        { label: '分', value: 'minutes' },
+        { label: '小时', value: 'hours' },
+      ]}
+      value={unit}
+      onChange={setUnit}
+    />
+  );
+
+  return (
+    <Modal
+      title={'执行动作'}
+      open
+      width={400}
+      onCancel={() => {
+        props.cancel();
+      }}
+      onOk={() => {
+        props.save(
+          {
+            time: value,
+            unit,
+          },
+          { name: `延迟 ${value} ${timeUnitEnum[unit]} 执行` },
+        );
+      }}
+    >
+      <InputNumber
+        placeholder={'请输入时间'}
+        addonAfter={TimeTypeAfter}
+        style={{ maxWidth: 220 }}
+        value={value}
+        onChange={(v) => setValue(v!)}
+        min={0}
+        max={59}
+      />
+    </Modal>
+  );
+});

+ 42 - 49
src/pages/rule-engine/Scene/Save/action/ListItem/Item.tsx

@@ -2,7 +2,6 @@ import { useState } from 'react';
 import Modal from '../Modal/add';
 import type { ActionsType } from '@/pages/rule-engine/Scene/typings';
 import { DeleteOutlined } from '@ant-design/icons';
-import { FormModel } from '@/pages/rule-engine/Scene/Save';
 import './index.less';
 import TriggerAlarm from '../TriggerAlarm';
 import { AddButton } from '@/pages/rule-engine/Scene/Save/components/Buttons';
@@ -16,6 +15,10 @@ interface ItemProps {
   name: number;
   data: ActionsType;
   type: ParallelType;
+  parallel: boolean;
+  options: any;
+  onUpdate: (data: any, options: any) => void;
+  onDelete: () => void;
 }
 
 const iconMap = new Map();
@@ -129,15 +132,7 @@ export default (props: ItemProps) => {
           </div>
         );
       default:
-        return (
-          <AddButton
-            onClick={() => {
-              setVisible(true);
-            }}
-          >
-            点击配置执行动作
-          </AddButton>
-        );
+        return null;
     }
   };
 
@@ -175,15 +170,7 @@ export default (props: ItemProps) => {
           </div>
         );
       default:
-        return (
-          <AddButton
-            onClick={() => {
-              setVisible(true);
-            }}
-          >
-            点击配置执行动作
-          </AddButton>
-        );
+        return null;
     }
   };
 
@@ -203,7 +190,12 @@ export default (props: ItemProps) => {
       );
     } else if (props?.data?.alarm?.mode === 'relieve') {
       return (
-        <div>
+        <div
+          className={'item-options-content'}
+          onClick={() => {
+            setVisible(true);
+          }}
+        >
           满足条件后将解除关联
           <a
             onClick={() => {
@@ -215,8 +207,28 @@ export default (props: ItemProps) => {
         </div>
       );
     } else if (props?.data?.executor === 'notify') {
-      return notifyRender(props?.data);
-    } else if (props?.data?.executor === 'device') {
+      return (
+        <div
+          className={'item-options-content'}
+          onClick={() => {
+            setVisible(true);
+          }}
+        >
+          {notifyRender(props?.data)}
+        </div>
+      );
+    } else if (props?.data?.executor === 'delay') {
+      return (
+        <div
+          className={'item-options-content'}
+          onClick={() => {
+            setVisible(true);
+          }}
+        >
+          {props.options.name}
+        </div>
+      );
+    } else if (props.data?.executor === 'device') {
       return deviceRender(props?.data);
     }
     return (
@@ -236,41 +248,20 @@ export default (props: ItemProps) => {
         <div className="item-options-warp">
           <div className="item-options-type">
             <img
-              style={{ width: 48 }}
+              style={{ width: 18 }}
               src={iconMap.get(
                 props?.data.executor === 'alarm' ? props?.data?.alarm?.mode : props?.data.executor,
               )}
             />
           </div>
-          <div
-            className={'item-options-content'}
-            onClick={() => {
-              setVisible(true);
-            }}
-          >
-            {contentRender()}
-          </div>
+          {contentRender()}
         </div>
         <div className="item-number">{props.name + 1}</div>
-        <div
-          className="item-delete"
-          onClick={() => {
-            const indexOf = FormModel.actions.findIndex((item) => item.key === props.data.key);
-            if (props.data.key && indexOf !== -1) {
-              FormModel.actions.splice(indexOf, 1);
-            }
-          }}
-        >
+        <div className="item-delete" onClick={props.onDelete}>
           <DeleteOutlined />
         </div>
       </div>
-      {props.type === 'serial' ? (
-        props.data.terms?.length ? (
-          <div></div>
-        ) : (
-          <div>添加过滤条件</div>
-        )
-      ) : null}
+      {props.type === 'serial' ? props.parallel ? <div>添加过滤条件</div> : <div></div> : null}
       {visible && (
         <Modal
           name={props.name}
@@ -278,10 +269,12 @@ export default (props: ItemProps) => {
           close={() => {
             setVisible(false);
           }}
-          save={(data: ActionsType) => {
-            FormModel.actions[props.name] = data;
+          save={(data: ActionsType, options) => {
+            // FormModel.actions[props.name] = data;
+            props.onUpdate(data, options);
             setVisible(false);
           }}
+          type={props.type}
         />
       )}
       {triggerVisible && (

+ 29 - 7
src/pages/rule-engine/Scene/Save/action/ListItem/List.tsx

@@ -5,10 +5,12 @@ import './index.less';
 import type { ActionsType } from '@/pages/rule-engine/Scene/typings';
 import Item from './Item';
 import type { ParallelType } from './Item';
-import { FormModel } from '@/pages/rule-engine/Scene/Save';
 interface ListProps {
   type: ParallelType;
   actions: ActionsType[];
+  parallel: boolean;
+  onAdd: (data: any) => void;
+  onDelete: (index: number) => void;
 }
 
 export default (props: ListProps) => {
@@ -22,7 +24,24 @@ export default (props: ListProps) => {
   return (
     <div className="action-list-content">
       {actions.map((item, index) => (
-        <Item name={index} data={item} type={props.type} key={item.key} />
+        <Item
+          name={index}
+          data={item}
+          type={props.type}
+          key={item.key}
+          parallel={props.parallel}
+          options={item.options}
+          onDelete={() => {
+            props.onDelete(index);
+          }}
+          onUpdate={(data, options) => {
+            props.onAdd({
+              ...data,
+              options,
+            });
+            setVisible(false);
+          }}
+        />
       ))}
       <AddButton
         onClick={() => {
@@ -33,6 +52,7 @@ export default (props: ListProps) => {
       </AddButton>
       {visible && (
         <Modal
+          type={props.type}
           name={props.actions.length + 1}
           data={{
             key: `${props.type}_${props.actions.length}`,
@@ -40,7 +60,7 @@ export default (props: ListProps) => {
           close={() => {
             setVisible(false);
           }}
-          save={(data: any) => {
+          save={(data: any, options) => {
             const { type, ...extra } = data;
             const item: ActionsType = {
               ...extra,
@@ -49,11 +69,13 @@ export default (props: ListProps) => {
               alarm: {
                 mode: data.type,
               },
+              options,
             };
-            const index = FormModel?.actions.findIndex((i) => {
-              return i.key === item.key ? item : i;
-            });
-            FormModel.actions[index] = { ...item };
+            // const index = FormModel?.actions.findIndex((i) => {
+            //   return i.key === item.key ? item : i;
+            // });
+            // FormModel.actions[index] = { ...item };
+            props.onAdd(item);
             setVisible(false);
           }}
         />

+ 3 - 2
src/pages/rule-engine/Scene/Save/action/ListItem/index.less

@@ -27,15 +27,16 @@
       align-items: center;
       justify-content: center;
       width: 48px;
+      height: 48px;
       margin-right: 8px;
-      background: rgba(47, 84, 235, 0.08);
+      background-color: #fafafa;
     }
 
     .item-options-content {
       display: flex;
       align-items: center;
       padding: 0 8px;
-      background: rgba(47, 84, 235, 0.08);
+      background: #fafafa;
       cursor: pointer;
       div {
         padding: 6px 10px;

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

@@ -2,14 +2,16 @@ import { Modal, Form } from 'antd';
 import ActionsTypeComponent from '@/pages/rule-engine/Scene/Save/components/TriggerWay/actionsType';
 import { useEffect, useState } from 'react';
 import Notify from '../notify';
-import type { ActionsType } from '@/pages/rule-engine/Scene/typings';
+import type { ActionsType, ParallelType } from '@/pages/rule-engine/Scene/typings';
 import Device from '../DeviceOutput';
+import Delay from '../Delay';
 
 interface Props {
   close: () => void;
   save: (data: any, options?: any) => void;
   data: Partial<ActionsType>;
   name: number;
+  type: ParallelType;
 }
 
 export default (props: Props) => {
@@ -55,6 +57,26 @@ export default (props: Props) => {
             }}
           />
         );
+      case 'delay':
+        return (
+          <Delay
+            value={props.data?.delay || {}}
+            save={(data: any, options) => {
+              setActionType('');
+              props.save(
+                {
+                  type: 'delay',
+                  key: props.data.key || `delay_${new Date().getTime()}`,
+                  ...data,
+                },
+                options,
+              );
+            }}
+            cancel={() => {
+              setActionType('');
+            }}
+          />
+        );
       default:
         return null;
     }
@@ -83,7 +105,7 @@ export default (props: Props) => {
           required
           rules={[{ required: true, message: '请选择类型' }]}
         >
-          <ActionsTypeComponent />
+          <ActionsTypeComponent type={props.type} />
         </Form.Item>
       </Form>
       {actionTypeComponent(actionType)}

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

@@ -4,14 +4,15 @@ import { FormModel } from '@/pages/rule-engine/Scene/Save';
 import ShakeLimit from '../components/ShakeLimit';
 import './index.less';
 import { Observer } from '@formily/react';
-import { get } from 'lodash';
-import type { ShakeLimitType } from '../../typings';
+import { get, set } from 'lodash';
+import type { ShakeLimitType, BranchesThen } from '../../typings';
 
 const { Panel } = Collapse;
 
 interface ActionsProps {
-  name?: (string | number)[];
+  name: number;
   openShakeLimit?: boolean;
+  thenOptions: BranchesThen[];
 }
 
 export default (props: ActionsProps) => {
@@ -22,7 +23,10 @@ export default (props: ActionsProps) => {
         {props.openShakeLimit ? (
           <Observer>
             {() => {
-              const data: ShakeLimitType = get(FormModel, [...props.name!, 'shakeLimit']);
+              const data: ShakeLimitType | undefined = get(FormModel.branches, [
+                props.name!,
+                'shakeLimit',
+              ]);
               return (
                 <ShakeLimit
                   enabled={data?.enabled}
@@ -30,7 +34,7 @@ export default (props: ActionsProps) => {
                   threshold={data?.threshold}
                   alarmFirst={data?.alarmFirst}
                   onChange={(type, value) => {
-                    data[type] = value;
+                    data![type] = value;
                   }}
                 />
               );
@@ -50,12 +54,40 @@ export default (props: ActionsProps) => {
           >
             <div className="actions-list">
               <Observer>
-                {() => (
-                  <List
-                    type="serial"
-                    actions={FormModel.actions.filter((item) => 'terms' in item)}
-                  />
-                )}
+                {() => {
+                  const parallelThens = props.thenOptions.filter((item) => !item.parallel);
+                  console.log(parallelThens);
+
+                  return (
+                    <List
+                      type="serial"
+                      parallel={false}
+                      actions={parallelThens.length ? parallelThens[0].actions : []}
+                      onAdd={(actionItem) => {
+                        console.log(parallelThens);
+                        if (parallelThens[0]) {
+                          parallelThens[0].actions = parallelThens[0].actions.map((aItem) => {
+                            if (aItem.key === actionItem.key) {
+                              return actionItem;
+                            }
+                            return aItem;
+                          });
+                          set(FormModel, ['branches', props.name, 'then'], parallelThens);
+                        } else {
+                          parallelThens.push({
+                            parallel: false,
+                            actions: [actionItem],
+                          });
+                          set(FormModel, ['branches', props.name, 'then'], parallelThens);
+                        }
+                      }}
+                      onDelete={(_index) => {
+                        parallelThens[0].actions.splice(_index, 1);
+                        set(FormModel, ['branches', props.name, 'then'], parallelThens);
+                      }}
+                    />
+                  );
+                }}
               </Observer>
             </div>
           </Panel>
@@ -69,12 +101,37 @@ export default (props: ActionsProps) => {
           >
             <div className="actions-list">
               <Observer>
-                {() => (
-                  <List
-                    type="parallel"
-                    actions={FormModel.actions.filter((item) => !('terms' in item))}
-                  />
-                )}
+                {() => {
+                  const parallelThens = props.thenOptions.filter((item) => item.parallel);
+                  return (
+                    <List
+                      type="parallel"
+                      parallel={true}
+                      actions={parallelThens.length ? parallelThens[0].actions : []}
+                      onAdd={(actionItem) => {
+                        if (parallelThens[0]) {
+                          parallelThens[0].actions = parallelThens[0].actions.map((aItem) => {
+                            if (aItem.key === actionItem.key) {
+                              return actionItem;
+                            }
+                            return aItem;
+                          });
+                          set(FormModel, ['branches', props.name, 'then'], parallelThens);
+                        } else {
+                          parallelThens.push({
+                            parallel: true,
+                            actions: [actionItem],
+                          });
+                          set(FormModel, ['branches', props.name, 'then'], parallelThens);
+                        }
+                      }}
+                      onDelete={(_index) => {
+                        parallelThens[0].actions.splice(_index, 1);
+                        set(FormModel, ['branches', props.name, 'then'], parallelThens);
+                      }}
+                    />
+                  );
+                }}
               </Observer>
             </div>
           </Panel>

+ 7 - 3
src/pages/rule-engine/Scene/Save/action/notify/NotifyConfig.tsx

@@ -7,7 +7,11 @@ import { ExtraNoticeConfigCard } from '@/components/ProTableCard/CardItems/notic
 import { observer } from '@formily/react';
 import { NotifyModel } from './index';
 
-export default observer(() => {
+interface Props {
+  type: string;
+}
+
+export default observer((props: Props) => {
   const actionRef = useRef<ActionType>();
   const [searchParam, setSearchParam] = useState({});
 
@@ -85,7 +89,7 @@ export default observer(() => {
                         {
                           column: 'type',
                           termType: 'eq',
-                          value: NotifyModel.notify?.notifyType || '',
+                          value: NotifyModel.notify?.notifyType || props.type,
                         },
                       ],
                     },
@@ -94,7 +98,7 @@ export default observer(() => {
                     {
                       column: 'type',
                       termType: 'eq',
-                      value: NotifyModel.notify?.notifyType || '',
+                      value: NotifyModel.notify?.notifyType || props.type,
                     },
                   ],
               sorts: [{ name: 'createTime', order: 'desc' }],

+ 12 - 5
src/pages/rule-engine/Scene/Save/action/notify/NotifyTemplate.tsx

@@ -7,7 +7,11 @@ import { ExtraNoticeTemplateCard } from '@/components/ProTableCard/CardItems/not
 import { observer } from '@formily/react';
 import { NotifyModel } from './index';
 
-export default observer(() => {
+interface Props {
+  type: string;
+}
+
+export default observer((props: Props) => {
   const actionRef = useRef<ActionType>();
   const [searchParam, setSearchParam] = useState({});
 
@@ -75,10 +79,13 @@ export default observer(() => {
             },
           }}
           request={async (params) => {
-            const resp = await queryMessageTemplatePaging(NotifyModel.notify?.notifierId || '', {
-              ...params,
-              sorts: [{ name: 'createTime', order: 'desc' }],
-            });
+            const resp = await queryMessageTemplatePaging(
+              NotifyModel.notify?.notifierId || props?.type,
+              {
+                ...params,
+                sorts: [{ name: 'createTime', order: 'desc' }],
+              },
+            );
             return {
               code: resp.message,
               result: {

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

@@ -65,9 +65,9 @@ export default observer((props: Props) => {
       case 'way':
         return <NotifyWay ref={WayRef} value={NotifyModel.notify?.notifyType} />;
       case 'config':
-        return <NotifyConfig />;
+        return <NotifyConfig type={NotifyModel.notify.notifyType || ''} />;
       case 'template':
-        return <NotifyTemplate />;
+        return <NotifyTemplate type={NotifyModel.notify.notifyType || ''} />;
       case 'variable':
         return <VariableDefinitions name={props.name} ref={VariableRef} />;
       default:

+ 40 - 11
src/pages/rule-engine/Scene/Save/components/Buttons/Dropdown.tsx

@@ -14,10 +14,12 @@ interface DropdownButtonProps {
   className?: string;
   placeholder?: string;
   value?: string;
-  onChange?: (value?: string) => void;
+  onChange?: (value?: string, item?: any) => void;
   options: DropdownButtonOptions[];
   isTree?: boolean;
   type: 'param' | 'termType' | 'value' | 'type';
+  fieldNames?: any;
+  showLabelKey?: string;
 }
 
 const TypeStyle = {
@@ -31,24 +33,38 @@ const DropdownButton = (props: DropdownButtonProps) => {
   const [myValue, setMyValue] = useState(props.value);
   const [label, setLabel] = useState('');
   const [loading, setLoading] = useState(false);
+  const [open, setOpen] = useState(false);
 
   const typeClassName = TypeStyle[props.type];
 
   const menuOnSelect = ({ key, item }: { key: string; item: any }) => {
-    props.onChange?.(key);
+    setOpen(false);
+
+    props.onChange?.(key, item);
     setMyValue(key);
-    setLabel(item.props.title);
+    let titleKey = 'title';
+    if (props.showLabelKey) {
+      titleKey = props.showLabelKey;
+    }
+    setLabel(item.props[titleKey]);
   };
 
   const treeSelect = (selectedKeys: (string | number)[], e: any) => {
-    props.onChange?.(selectedKeys[0] as string);
+    setOpen(false);
+
+    props.onChange?.(selectedKeys[0] as string, e);
     setMyValue(selectedKeys[0] as string);
-    setLabel(e.node.title);
+
+    let titleKey = 'title';
+    if (props.showLabelKey) {
+      titleKey = props.showLabelKey;
+    }
+    setLabel(e.node[titleKey]);
   };
 
   const menuOptions = {
     selectedKeys: myValue ? [myValue] : [],
-    items: props.options.map((item) => ({ ...item, label: item.title })),
+    items: props.options?.map((item) => ({ ...item, label: item.title })) || [],
     onClick: menuOnSelect,
   };
 
@@ -58,9 +74,11 @@ const DropdownButton = (props: DropdownButtonProps) => {
         selectedKeys={myValue ? [myValue] : []}
         onSelect={treeSelect}
         treeData={props.options}
+        fieldNames={props.fieldNames}
+        height={500}
       />
     );
-  }, [props.options]);
+  }, [props.options, myValue]);
 
   const _options = !props.isTree ? { menu: menuOptions } : { dropdownRender: () => DropdownRender };
 
@@ -68,7 +86,12 @@ const DropdownButton = (props: DropdownButtonProps) => {
     let isLabel = false;
     return data.some((item) => {
       if (item.key === value) {
-        setLabel(item.title);
+        let titleKey = 'title';
+        if (props.showLabelKey) {
+          titleKey = props.showLabelKey;
+        }
+        setLabel(item[titleKey]);
+        setLoading(false);
         isLabel = true;
       } else if (item.children) {
         isLabel = findLable(value, item.children);
@@ -79,17 +102,23 @@ const DropdownButton = (props: DropdownButtonProps) => {
 
   useEffect(() => {
     setMyValue(props.value);
+    if (!props.value) {
+      setLabel('');
+    } else {
+      setLoading(true);
+      findLable(props.value, props.options);
+    }
   }, [props.value]);
 
   useEffect(() => {
-    if (myValue && !loading) {
-      findLable(myValue, props.options);
+    if (props.value && !loading) {
+      findLable(props.value, props.options);
       setLoading(true);
     }
   }, [props.options]);
 
   return (
-    <Dropdown {..._options} trigger={['click']}>
+    <Dropdown {..._options} trigger={['click']} open={open} onOpenChange={setOpen}>
       <div className={classNames(styles['dropdown-button'], props.className, typeClassName)}>
         {label || props.placeholder}
       </div>

+ 216 - 27
src/pages/rule-engine/Scene/Save/components/Buttons/ParamsDropdown.tsx

@@ -1,50 +1,239 @@
-import { useEffect, useMemo, useState } from 'react';
-import { Dropdown, Tabs } from 'antd';
+import { useCallback, useEffect, useState } from 'react';
+import { Input, InputNumber, Menu } from 'antd';
 import classNames from 'classnames';
 import styles from './index.less';
+import MTimePicker from '../ParamsSelect/components/MTimePicker';
+import moment from 'moment';
+import ParamsSelect from '../ParamsSelect';
+import './index.less';
 
 interface ParamsDropdownProps {
   value?: any;
   source?: string;
   placeholder?: string;
-  onChange?: (value?: string) => void;
+  onChange?: (value: any, lb?: any) => void;
   isMetric?: boolean;
   metricOptions?: any[];
   type?: string;
   options?: any[];
+  metricsOptions?: any[];
+  name?: number;
+  valueType: string;
+  showLabelKey?: string;
 }
 
+interface MenusProps {
+  value: any;
+  options?: any[];
+  onChange: (value: any, lb: any) => void;
+}
+
+const Menus = (props: MenusProps) => {
+  const [value, setValue] = useState(props.value);
+
+  useEffect(() => {
+    console.log('menus-useEffect', props.value);
+
+    setValue(props.value);
+  }, [props.value]);
+
+  return (
+    <div className="no-menu-style">
+      <Menu
+        style={{
+          boxShadow: 'none',
+          border: 'none',
+        }}
+        selectedKeys={value ? [value] : undefined}
+        items={props.options}
+        onClick={({ key, item }: { key: any; item: any }) => {
+          const _item = props.options?.find((a) => a.value === item.props.value);
+          setValue(key);
+          if (_item) {
+            props.onChange(_item.value, _item.label);
+          }
+        }}
+      />
+    </div>
+  );
+};
+
 export default (props: ParamsDropdownProps) => {
-  const [label] = useState('');
-  //   const [myValue, setMyValue] = useState(undefined);
-  const [, setSource] = useState('');
-  const [activeKey, setActiveKey] = useState('value_1');
-
-  const tabsChange = (key: string) => {
-    setActiveKey(key);
-    if (key.includes('value')) {
-      setSource('value');
-    } else {
-      setSource('metric');
+  const [label, setLabel] = useState('');
+  const [myValue, setMyValue] = useState<any>(undefined);
+  const [activeKey, setActiveKey] = useState('manual');
+  const [open, setOpen] = useState(false);
+
+  const onValueChange = useCallback(
+    (value: any, _label: any) => {
+      setMyValue(value);
+      setLabel(_label);
+      const changeValue = {
+        value: value,
+        source: activeKey,
+      };
+      props.onChange?.(changeValue, _label);
+    },
+    [activeKey],
+  );
+
+  const renderNode = useCallback(
+    (type: string, _v: any, config: any) => {
+      let _value = _v;
+      if (config.name && Array.isArray(_v)) {
+        _value = _v[config.name];
+      }
+
+      switch (type) {
+        case 'int':
+        case 'long':
+        case 'float':
+        case 'double':
+          return (
+            <InputNumber
+              value={_value}
+              onChange={(e: any) => {
+                onValueChange(e, e);
+              }}
+              style={{ width: '100%' }}
+              placeholder={'请输入'}
+            />
+          );
+        case 'enum':
+          return <Menus value={_value} options={props.options} onChange={onValueChange} />;
+        case 'boolean':
+          const _options = [
+            { label: '是', value: 'true', key: 'true' },
+            { label: '否', value: 'false', key: 'false' },
+          ];
+          return (
+            <Menus
+              value={_value !== undefined ? String(_value) : undefined}
+              options={_options}
+              onChange={(v, l) => {
+                onValueChange(v === 'true' ? true : false, l);
+              }}
+            />
+          );
+        case 'date':
+          return (
+            <MTimePicker
+              value={moment(_value, 'HH:mm:ss')}
+              onChange={(_: any, timeString: string) => {
+                onValueChange(timeString, timeString);
+              }}
+            />
+          );
+        default:
+          return (
+            <Input
+              value={_value}
+              placeholder={'请输入'}
+              onChange={(e) => {
+                const _iv = e.target.value;
+                onValueChange(_iv, _iv);
+              }}
+            />
+          );
+      }
+    },
+    [props],
+  );
+
+  // const findLable = (value: string, data: any[]): boolean => {
+  //   let isLabel = false;
+  //   return data.some((item) => {
+  //     if (item.key === value) {
+  //       let titleKey = 'title'
+  //       if (props.showLabelKey) {
+  //         titleKey = props.showLabelKey
+  //       }
+  //       setLabel(item[titleKey]);
+  //       isLabel = true;
+  //     } else if (item.children) {
+  //       isLabel = findLable(value, item.children);
+  //     }
+  //     return isLabel;
+  //   });
+  // };
+
+  useEffect(() => {
+    console.log('params-dropdown', props.value);
+
+    if (props.value?.value === undefined || props.value?.value === '') {
+      setLabel('');
+    } else if (props.valueType === 'boolean') {
+      if (props.name) {
+        if (props.value.value[props.name] === true) {
+          setLabel('是');
+        } else if (props.value.value[props.name] === false) {
+          setLabel('否');
+        } else {
+          setLabel('');
+        }
+      } else {
+        if (props.value.value === true) {
+          setLabel('是');
+        } else if (props.value.value === false) {
+          setLabel('否');
+        } else {
+          setLabel('');
+        }
+      }
+    } else if (['enum', 'boolean'].includes(props.valueType)) {
+      setLabel(props.name ? props.value.value[props.name] : props.value.value);
+    }
+    setMyValue(props.value?.value);
+    setActiveKey(props.value?.source);
+  }, [props.value]);
+
+  useEffect(() => {
+    if (props.options) {
+      // findLable(props.value?.value, props.options || [])
     }
-  };
+  }, [props.options]);
 
-  useEffect(() => {}, [props.value]);
+  const _itemList = [
+    {
+      key: 'manual',
+      label: '手动输入',
+      content: renderNode(props.valueType, myValue, props),
+    },
+  ];
 
-  const DropdownRender = useMemo(() => {
-    return (
-      <Tabs activeKey={activeKey} onChange={tabsChange}>
-        <Tabs.TabPane key="value_1"></Tabs.TabPane>
-        {props.isMetric && <Tabs.TabPane key="metric"></Tabs.TabPane>}
-      </Tabs>
-    );
-  }, [props.isMetric]);
+  if (props.isMetric) {
+    _itemList.push({
+      key: 'metric',
+      label: '指标值',
+      content: renderNode('enum', myValue, props),
+    });
+  }
 
   return (
-    <Dropdown dropdownRender={() => DropdownRender} trigger={['click']}>
-      <div className={classNames(styles['dropdown-button'], styles.value)}>
+    <ParamsSelect
+      value={myValue}
+      onChange={(value, source) => {
+        setActiveKey(source);
+        // props.onChange?.({
+        //   value,
+        //   source
+        // }, label )
+      }}
+      tabKey={activeKey}
+      itemList={_itemList}
+      style={{ width: 'auto', display: 'inline-block' }}
+      type={props.valueType}
+      open={open}
+      openChange={setOpen}
+    >
+      <div
+        className={classNames(styles['dropdown-button'], styles.value)}
+        onClick={() => {
+          setOpen(true);
+        }}
+      >
         {label || props.placeholder}
       </div>
-    </Dropdown>
+    </ParamsSelect>
   );
 };

+ 20 - 0
src/pages/rule-engine/Scene/Save/components/Buttons/index.less

@@ -52,3 +52,23 @@
 .type {
   padding: 5px 10px;
 }
+
+.params-dropdown {
+  .ant-dropdown-menu {
+    box-shadow: none;
+  }
+}
+
+.no-menu-style {
+  max-height: 400px;
+  overflow-x: hidden;
+  overflow-y: auto;
+
+  .ant-menu {
+    .ant-menu-item {
+      height: 32px !important;
+      margin: 0 !important;
+      line-height: 32px !important;
+    }
+  }
+}

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

@@ -4,7 +4,7 @@
   // margin-bottom: 20px;
   .select-container {
     position: absolute;
-    top: 32px;
+    top: calc(100% + 4px);
     right: auto;
     left: 0;
     z-index: 99;

+ 27 - 12
src/pages/rule-engine/Scene/Save/components/ParamsSelect/index.tsx

@@ -20,6 +20,10 @@ interface Props {
   style?: object;
   tabKey: string;
   type?: string;
+  className?: string | string[];
+  children?: ReactNode;
+  open?: boolean;
+  openChange?: (open: boolean) => void;
 }
 
 export default (props: Props) => {
@@ -37,10 +41,17 @@ export default (props: Props) => {
     setValue(props.value);
   }, [props.value]);
 
+  useEffect(() => {
+    if (props.open !== undefined) {
+      setVisible(props.open);
+    }
+  }, [props.open]);
+
   const handleClick = (e: any) => {
     if (visible && e.target) {
       if (!(wrapperRef.current && wrapperRef.current.contains(e.target))) {
         setVisible(false);
+        props.openChange?.(false);
       }
     }
   };
@@ -58,18 +69,22 @@ export default (props: Props) => {
 
   return (
     <div className={'select-wrapper'} ref={wrapperRef} style={props.style}>
-      <Input
-        suffix={<DownOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
-        {...props.inputProps}
-        value={value}
-        onChange={(e) => {
-          setValue(e.target.value);
-          props.onChange(e.target.value, tabKey);
-        }}
-        onFocus={() => {
-          setVisible(true);
-        }}
-      />
+      {props.children ? (
+        props.children
+      ) : (
+        <Input
+          suffix={<DownOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
+          {...props.inputProps}
+          value={value}
+          onChange={(e) => {
+            setValue(e.target.value);
+          }}
+          onFocus={() => {
+            setVisible(true);
+            props.openChange?.(true);
+          }}
+        />
+      )}
       {visible && (
         <div
           className={'select-container'}

+ 1 - 1
src/pages/rule-engine/Scene/Save/components/TimingTrigger/whenOption.tsx

@@ -55,7 +55,7 @@ export default (props: TimerWhenProps) => {
           }
         }}
       >
-        全部
+        每天
       </div>
       {options.map((item) => {
         return (

+ 23 - 19
src/pages/rule-engine/Scene/Save/components/TriggerWay/actionsType.tsx

@@ -1,6 +1,7 @@
 import { useEffect, useState } from 'react';
 import classNames from 'classnames';
 import './index.less';
+import { ParallelType } from '../../../typings';
 
 interface ActionsTypeProps {
   value?: string;
@@ -8,6 +9,7 @@ interface ActionsTypeProps {
   onChange?: (type: string) => void;
   onSelect?: (type: string) => void;
   disabled?: boolean;
+  type: ParallelType;
 }
 
 export enum ActionsTypeEnum {
@@ -68,26 +70,28 @@ export default (props: ActionsTypeProps) => {
 
   return (
     <div className={classNames('trigger-way-warp', props.className, { disabled: props.disabled })}>
-      {TypeList.map((item) => (
-        <div
-          key={item.value}
-          className={classNames('trigger-way-item', {
-            active: type === item.value,
-          })}
-          style={{ width: 237 }}
-          onClick={() => {
-            onSelect(item.value);
-          }}
-        >
-          <div className={'way-item-title'}>
-            <p>{item.label}</p>
-            <span>{item.tip}</span>
+      {TypeList.map((item) =>
+        props.type === 'parallel' && item.value === 'delay' ? null : (
+          <div
+            key={item.value}
+            className={classNames('trigger-way-item', {
+              active: type === item.value,
+            })}
+            style={{ width: 237 }}
+            onClick={() => {
+              onSelect(item.value);
+            }}
+          >
+            <div className={'way-item-title'}>
+              <p>{item.label}</p>
+              <span>{item.tip}</span>
+            </div>
+            <div className={'way-item-image'}>
+              <img width={48} src={item.image} />
+            </div>
           </div>
-          <div className={'way-item-image'}>
-            <img width={48} src={item.image} />
-          </div>
-        </div>
-      ))}
+        ),
+      )}
     </div>
   );
 };

+ 15 - 12
src/pages/rule-engine/Scene/Save/device/deviceList.tsx

@@ -1,7 +1,7 @@
 import { ProTableCard } from '@/components';
 import SearchComponent from '@/components/SearchComponent';
 import type { DeviceInstance } from '@/pages/device/Instance/typings';
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { service } from '@/pages/device/Instance/index';
 import { isNoCommunity } from '@/utils/util';
@@ -16,6 +16,13 @@ export default observer(() => {
   const actionRef = useRef<ActionType>();
   const intl = useIntl();
   const [searchParam, setSearchParam] = useState({});
+  const [selectorValues, setSelectorValues] = useState<any[]>([]);
+
+  useEffect(() => {
+    if (DeviceModel.selector === 'device') {
+      setSelectorValues(DeviceModel.selectorValues?.map((item) => item.id) || []);
+    }
+  }, [DeviceModel.selectorValues]);
 
   const columns: ProColumns<DeviceInstance>[] = [
     {
@@ -246,17 +253,13 @@ export default observer(() => {
           onlyCard={true}
           tableAlertRender={false}
           rowSelection={{
-            type: 'radio',
-            selectedRowKeys: [DeviceModel.deviceId],
-            onChange: (_, selectedRows) => {
-              if (selectedRows.length) {
-                const item = selectedRows[0];
-                DeviceModel.deviceId = item.id;
-                DeviceModel.selectorValues = [{ value: DeviceModel.deviceId, name: item.name }];
-              } else {
-                DeviceModel.deviceId = '';
-                DeviceModel.selectorValues = [];
-              }
+            selectedRowKeys: selectorValues,
+            onChange(selectedRowKeys, selectedRows) {
+              setSelectorValues(selectedRowKeys);
+              DeviceModel.selectorValues = selectedRows.map((item) => ({
+                name: item.name,
+                value: item.id,
+              }));
             },
           }}
           request={(params) =>

+ 10 - 3
src/pages/rule-engine/Scene/Save/device/index.tsx

@@ -5,6 +5,7 @@ import { useState } from 'react';
 import { Observer, observer } from '@formily/react';
 import AddModel from './addModel';
 import { FormModel } from '@/pages/rule-engine/Scene/Save';
+import classNames from 'classnames';
 
 export default observer(() => {
   const [visible, setVisible] = useState(false);
@@ -41,7 +42,7 @@ export default observer(() => {
       <div style={{ marginBottom: 16 }}>
         <Observer>
           {() => {
-            const label = handleLabel(FormModel.options);
+            const label = handleLabel(FormModel.trigger?.options);
             return (
               <AddButton
                 style={{ width: '100%' }}
@@ -49,7 +50,13 @@ export default observer(() => {
                   setVisible(true);
                 }}
               >
-                <div className="trigger-options-content">{label}</div>
+                <div
+                  className={classNames('trigger-options-content', {
+                    'is-add': !!Object.keys(FormModel.trigger?.options || {}).length,
+                  })}
+                >
+                  {label}
+                </div>
               </AddButton>
             );
           }}
@@ -61,7 +68,7 @@ export default observer(() => {
           value={FormModel.trigger?.device}
           onSave={(data, options) => {
             setVisible(false);
-            FormModel['options'] = options;
+            FormModel.trigger!['options'] = options;
             FormModel.trigger!.device = data;
           }}
           onCancel={() => {

+ 4 - 4
src/pages/rule-engine/Scene/Save/device/type.tsx

@@ -101,11 +101,11 @@ export default forwardRef((props, ref) => {
     }
 
     if (DeviceModel.metadata.properties?.length) {
-      const _readProperty = DeviceModel.metadata.properties.filter((item) =>
-        item.expands.type.includes('read'),
+      const _readProperty = DeviceModel.metadata.properties.filter(
+        (item) => !!item.expands.type?.includes('read'),
       );
-      const _writeProperty = DeviceModel.metadata.properties.filter((item) =>
-        item.expands.type.includes('write'),
+      const _writeProperty = DeviceModel.metadata.properties.filter(
+        (item) => !!item.expands.type?.includes('write'),
       );
       setReadProperty(_readProperty);
       setWriteProperty(_writeProperty);

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

@@ -1,4 +1,7 @@
 .trigger-options-content {
   display: flex;
   gap: 16px;
+  &.is-add {
+    color: rgba(#000, 0.8);
+  }
 }

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

@@ -6,17 +6,28 @@ import Manual from '../Save/manual/index';
 import Timer from '../Save/timer/index';
 import { TitleComponent } from '@/components';
 import { observable } from '@formily/reactive';
+import { observer } from '@formily/react';
 import type { FormModelType } from '@/pages/rule-engine/Scene/typings';
-import { useEffect } from 'react';
+import { useEffect, useCallback } from 'react';
 import { service } from '@/pages/rule-engine/Scene';
 import './index.less';
+import { onlyMessage } from '@/utils/util';
+import { useHistory } from 'umi';
+import { getMenuPathByCode } from '@/utils/menu';
 
 export const FormModel = observable<FormModelType>({
   trigger: {
     type: '',
+    options: {},
   },
   actions: [],
-  options: {},
+  options: {
+    terms: [
+      {
+        terms: [],
+      },
+    ],
+  },
   branches: [
     {
       when: [
@@ -25,7 +36,9 @@ export const FormModel = observable<FormModelType>({
             {
               column: undefined,
               value: undefined,
+              termType: undefined,
               key: 'params_1',
+              type: 'and',
             },
           ],
           type: 'and',
@@ -55,10 +68,11 @@ export const FormModel = observable<FormModelType>({
   ],
 });
 
-export default () => {
+export default observer(() => {
   const location = useLocation();
   const triggerType = location?.query?.triggerType || '';
   const id = location?.query?.id || '';
+  const history = useHistory();
 
   useEffect(() => {
     if (id) {
@@ -72,6 +86,7 @@ export default () => {
   }, [id]);
 
   const triggerRender = (type: string) => {
+    FormModel.trigger!.type = type;
     switch (type) {
       case 'device':
         return (
@@ -96,6 +111,16 @@ export default () => {
     }
   };
 
+  const submit = useCallback(() => {
+    service.updateScene(FormModel).then((res) => {
+      if (res.status === 200) {
+        onlyMessage('操作成功', 'success');
+        const url = getMenuPathByCode('rule-engine/Scene');
+        history.push(url);
+      }
+    });
+  }, [id, FormModel]);
+
   return (
     <PageContainer>
       <Card>
@@ -108,7 +133,7 @@ export default () => {
             <Input.TextArea showCount maxLength={200} placeholder={'请输入说明'} rows={4} />
           </Form.Item>
           <Form.Item>
-            <Button type="primary" htmlType="submit">
+            <Button type="primary" htmlType="submit" onClick={submit}>
               保存
             </Button>
           </Form.Item>
@@ -116,4 +141,4 @@ export default () => {
       </Card>
     </PageContainer>
   );
-};
+});

+ 11 - 4
src/pages/rule-engine/Scene/Save/manual/index.tsx

@@ -1,8 +1,15 @@
-import Actions from '../action';
-export default () => {
+import Action from '../action';
+import { Observer, observer } from '@formily/react';
+import { FormModel } from '@/pages/rule-engine/Scene/Save';
+
+export default observer(() => {
   return (
     <div>
-      <Actions />
+      <Observer>
+        {() => (
+          <Action thenOptions={FormModel.branches ? FormModel.branches[0].then : []} name={0} />
+        )}
+      </Observer>
     </div>
   );
-};
+});

+ 51 - 38
src/pages/rule-engine/Scene/Save/terms/branchItem.tsx

@@ -1,8 +1,8 @@
-import { observer, Observer } from '@formily/react';
-import { useState } from 'react';
+import { observer } from '@formily/react';
+import { useState, useEffect } from 'react';
 import { FormModel } from '@/pages/rule-engine/Scene/Save';
 import { PlusCircleOutlined, DeleteOutlined } from '@ant-design/icons';
-import type { ActionBranchesProps } from '@/pages/rule-engine/Scene/typings';
+import type { ActionBranchesProps, TermsType } from '@/pages/rule-engine/Scene/typings';
 import Term from './term';
 import Actions from '@/pages/rule-engine/Scene/Save/action';
 import classNames from 'classnames';
@@ -11,17 +11,24 @@ interface BranchesItemProps {
   name: number;
   data: ActionBranchesProps;
   isFrist: boolean;
+  onDelete: () => void;
 }
 
 export default observer((props: BranchesItemProps) => {
   const [deleteVisible, setDeleteVisible] = useState(false);
+  const [when, setWhen] = useState<TermsType[]>([]);
 
-  const deleteTerms = (index: number) => {
-    FormModel.branches?.splice(index, 1);
-  };
+  useEffect(() => {
+    if (props.data.when) {
+      setWhen(props.data.when);
+    }
+  }, [props.data]);
 
   const addWhen = (index: number) => {
     const lastBranch = FormModel.branches![index].when;
+    FormModel.options?.terms.push({
+      terms: [],
+    });
     lastBranch.push({
       terms: [
         {
@@ -60,45 +67,51 @@ export default observer((props: BranchesItemProps) => {
         {!props.isFrist && props.data.when?.length ? (
           <div
             className={classNames('terms-params-delete denger', { show: deleteVisible })}
-            onClick={() => deleteTerms(props.name)}
+            onClick={props.onDelete}
           >
             <DeleteOutlined />
           </div>
         ) : null}
         <div className="actions-terms-list">
-          <Observer>
-            {() =>
-              props.data.when?.length ? (
-                props.data.when.map((data, dIndex) => {
-                  return (
-                    <Term
-                      pName={[props.name, 'when']}
-                      name={dIndex}
-                      data={data}
-                      key={data.key}
-                      isLast={dIndex === props.data.when!.length - 1}
-                    />
-                  );
-                })
-              ) : (
-                <span
-                  style={{
-                    fontSize: 14,
-                    color: '#2F54EB',
-                    cursor: 'pointer',
-                    padding: props.isFrist ? '16px 0' : 0,
-                  }}
-                  onClick={() => addWhen(props.name)}
-                >
-                  {' '}
-                  <PlusCircleOutlined style={{ padding: 4 }} /> 添加过滤条件
-                </span>
-              )
-            }
-          </Observer>
+          {when.length ? (
+            when.map((item, dIndex) => (
+              <Term
+                pName={[props.name, 'when']}
+                name={dIndex}
+                data={item}
+                key={item.key}
+                isLast={dIndex === when!.length - 1}
+                onValueChange={(data) => {
+                  FormModel.branches![props.name].when[dIndex] = {
+                    ...FormModel.branches![props.name].when[dIndex],
+                    ...data,
+                  };
+                }}
+                onLabelChange={(options) => {
+                  FormModel.options!.terms[props.name] = options;
+                }}
+                onDelete={() => {
+                  FormModel.branches![props.name].when.splice(dIndex, 1);
+                }}
+              />
+            ))
+          ) : (
+            <span
+              style={{
+                fontSize: 14,
+                color: '#2F54EB',
+                cursor: 'pointer',
+                padding: props.isFrist ? '16px 0' : 0,
+              }}
+              onClick={() => addWhen(props.name)}
+            >
+              {' '}
+              <PlusCircleOutlined style={{ padding: 4 }} /> 添加过滤条件
+            </span>
+          )}
         </div>
         <div className="actions-branchs">
-          <Actions openShakeLimit={true} name={['branches', props.name]} />
+          <Actions openShakeLimit={true} name={props.name} thenOptions={props.data.then} />
         </div>
       </div>
     </div>

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

@@ -103,7 +103,7 @@
 
   .term-type-warp {
     display: inline-block;
-    width: 60px;
+    width: 50px;
     margin: 0 16px;
     .term-type {
       padding-top: 4px;

+ 33 - 3
src/pages/rule-engine/Scene/Save/terms/index.tsx

@@ -1,18 +1,39 @@
 import { useEffect } from 'react';
 import { TitleComponent } from '@/components';
 import { observer, Observer } from '@formily/react';
+import { model } from '@formily/reactive';
 import { FormModel } from '@/pages/rule-engine/Scene/Save';
 import BranchItem from './branchItem';
+import { service } from '@/pages/rule-engine/Scene/index';
+import type { TriggerType } from '@/pages/rule-engine/Scene/typings';
+
+interface TermsModelProps {
+  columnOptions: any[];
+}
+
+export const TermsModel = model<TermsModelProps>({
+  columnOptions: [],
+});
 
 export default observer(() => {
-  const queryColumn = () => {};
+  const queryColumn = (data: TriggerType) => {
+    service.getParseTerm({ trigger: data }).then((res) => {
+      TermsModel.columnOptions = res;
+    });
+  };
 
   useEffect(() => {
+    console.log('terms-useEffect', FormModel.trigger?.device);
+
     if (FormModel.trigger?.device) {
-      queryColumn();
+      queryColumn(FormModel.trigger);
     }
   }, [FormModel.trigger?.device]);
 
+  useEffect(() => {
+    console.log('terms-useEffect-branches', FormModel.branches);
+  }, [FormModel.branches]);
+
   return (
     <div className="actions-terms">
       <TitleComponent style={{ fontSize: 14 }} data="触发条件" />
@@ -20,7 +41,16 @@ export default observer(() => {
         {() =>
           FormModel.branches?.map((item, index) => {
             const isFrist = index === 0;
-            return <BranchItem data={item} isFrist={isFrist} name={index} />;
+            return (
+              <BranchItem
+                data={item}
+                isFrist={isFrist}
+                name={index}
+                onDelete={() => {
+                  FormModel.branches?.splice(index, 1);
+                }}
+              />
+            );
           })
         }
       </Observer>

+ 261 - 37
src/pages/rule-engine/Scene/Save/terms/paramsItem.tsx

@@ -1,47 +1,159 @@
-import { useState } from 'react';
-import type { TermsType } from '@/pages/rule-engine/Scene/typings';
+import { useEffect, useState, useCallback } from 'react';
+import type { TermsType, TermsVale } from '@/pages/rule-engine/Scene/typings';
 import { DropdownButton, ParamsDropdown } from '@/pages/rule-engine/Scene/Save/components/Buttons';
 import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
 import classNames from 'classnames';
 import { observer } from '@formily/react';
-import { FormModel } from '@/pages/rule-engine/Scene/Save';
-import { get, set } from 'lodash';
 import './index.less';
+import { Space } from 'antd';
 
 interface ParamsItemProps {
   data: TermsType;
   pName: (number | string)[];
   name: number;
   isLast: boolean;
+  options: any[];
+  onValueChange?: (value: TermsType) => void;
+  onLableChange?: (label: string) => void;
+  onAdd: () => void;
+  onDelete: () => void;
 }
 
-export default observer((props: ParamsItemProps) => {
+const DoubleFilter = ['nbtw', 'btw'];
+const ParasmItem = observer((props: ParamsItemProps) => {
   const [deleteVisible, setDeleteVisible] = useState(false);
-  const [paramOptions] = useState([]);
-  const [termTypeOptions] = useState([]);
-  const [valueOptions] = useState([]);
-
-  const deleteItem = () => {
-    const data = get(FormModel.branches, [...props.pName, 'terms']);
-    const indexOf = data?.findIndex((item: TermsType) => item.key === props.data.key);
-    if (indexOf !== undefined && indexOf !== -1) {
-      data!.splice(indexOf, 1);
+  const [paramOptions, setParamOptions] = useState<any[]>([]);
+  const [ttOptions, setTtOptions] = useState<any[]>([]);
+  const [valueOptions] = useState<any>(undefined);
+  const [metricsOptions, setMetricsOptions] = useState<any[]>([]);
+  const [valueType, setValueType] = useState('');
+
+  const [value, setValue] = useState<Partial<TermsVale> | undefined>({});
+  const [termType, setTermType] = useState('');
+  const [column, setColumn] = useState('');
+  const [label, setLabel] = useState<any[]>([undefined, undefined, {}]);
+
+  const valueChange = useCallback(
+    (_value: any) => {
+      props.onValueChange?.({ ..._value });
+    },
+    [column, termType, value],
+  );
+
+  const valueEventChange = useCallback(
+    (_v: any) => {
+      valueChange({
+        column,
+        termType,
+        value: _v,
+      });
+    },
+    [column, termType],
+  );
+
+  const paramChange = (item: any) => {
+    const node = item.node;
+    const _termTypeOptions: any[] =
+      node.termTypes?.map((tItem: any) => ({ title: tItem.name, key: tItem.id })) || [];
+    setTtOptions(_termTypeOptions);
+    setValueType(node.dataType);
+    if (node.metrics) {
+      // 指标值
+      const _metrics = node.metrics.map((mItem: any) => ({ title: mItem.name, key: mItem.id }));
+      setMetricsOptions(_metrics);
     }
-    set(FormModel.branches!, [...props.pName, 'terms'], data);
   };
 
-  const addItem = () => {
-    const key = `params_${new Date().getTime()}`;
-    const data = get(FormModel.branches, [...props.pName, 'terms']);
-    data?.push({
-      type: 'and',
-      column: undefined,
-      value: undefined,
-      termType: undefined,
-      key,
-    });
+  const handleName = (_data: any): 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>
+  );
+
+  const handleChildrenName = (_data: any[]): any[] => {
+    if (_data?.length > 0) {
+      return _data.map((it) => {
+        if (it.children) {
+          return {
+            ...it,
+            key: it.column,
+            value: it.column,
+            title: handleName(it),
+            disabled: true,
+            children: handleChildrenName(it.children),
+          };
+        }
+        return { ...it, key: it.column, title: handleName(it) };
+      });
+    } else {
+      return [];
+    }
   };
 
+  useEffect(() => {
+    setTermType(props.data.termType || '');
+    setValue(props.data.value);
+    setColumn(props.data.column || '');
+  }, [props.data]);
+
+  useEffect(() => {
+    const _data = props.options.map((item: any) => {
+      const disabled = item.children?.length > 0;
+      return {
+        ...item,
+        column: item.column,
+        key: item.column,
+        value: item.column,
+        title: handleName(item),
+        disabled: disabled,
+        children: handleChildrenName(item.children),
+      };
+    });
+
+    setParamOptions(_data);
+  }, [props.options]);
+
+  const handleOptionsLabel = useCallback(
+    (c: string, t: string, v: any) => {
+      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秒',
+      };
+      const typeKey = {
+        and: '并且',
+        or: '或者',
+      };
+      const typeStr = props.isLast ? '' : typeKey[props.data.type!];
+      if (DoubleFilter.includes(t)) {
+        const str = termsTypeKey[t].replace('_value', v[0]).replace('_value2', v[1]);
+        return `${c} ${str} ${typeStr}`;
+      }
+      const str = termsTypeKey[t].replace('_value', v);
+      return `${c} ${str} ${typeStr}`;
+    },
+    [props.isLast, props.data.type],
+  );
+
+  useEffect(() => {
+    const _v = Object.values(label[2]);
+    if (_v.length && label[1]) {
+      const _l = handleOptionsLabel(label[0], label[1], _v.length > 1 ? _v : _v[0]);
+      props.onLableChange?.(_l);
+    }
+  }, [label]);
+
   return (
     <div className="terms-params-item">
       <div
@@ -53,21 +165,131 @@ export default observer((props: ParamsItemProps) => {
           options={paramOptions}
           type="param"
           placeholder="请选择参数"
-          value={props.data.column}
+          value={column}
+          fieldNames={{
+            label: 'name',
+            value: 'column',
+          }}
+          showLabelKey="fullName"
+          isTree={true}
+          onChange={(_value, item) => {
+            setValue({
+              value: undefined,
+              source: 'manual',
+            });
+            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;
+              label[1] = _termTypeValue;
+              setTermType(_termTypeValue);
+            } else {
+              setTermType('');
+            }
+            label[0] = node.fullName;
+            setLabel([...label]);
+            valueChange({
+              column: _value,
+              value: {
+                value: undefined,
+                source: 'manual',
+              },
+              termType: _termTypeValue,
+            });
+          }}
         />
         <DropdownButton
-          options={termTypeOptions}
+          options={ttOptions}
           type="termType"
           placeholder="操作符"
-          value={props.data.termType}
-        />
-        <ParamsDropdown
-          options={valueOptions}
-          type="value"
-          placeholder="参数值"
-          value={props.data.value}
+          value={termType}
+          onChange={(v) => {
+            const _value = {
+              ...value,
+            };
+            if (value && DoubleFilter.includes(v!)) {
+              _value.value = [undefined, undefined];
+            } else {
+              _value.value = undefined;
+            }
+            setValue(_value);
+            setTermType(v!);
+
+            label[1] = v;
+            setLabel([...label]);
+
+            valueChange({
+              column: props.data.column,
+              value: value as TermsVale,
+              termType: v,
+            });
+          }}
         />
-        <div className={classNames('button-delete', { show: deleteVisible })} onClick={deleteItem}>
+        {DoubleFilter.includes(termType) ? (
+          <>
+            <ParamsDropdown
+              options={valueOptions}
+              metricsOptions={metricsOptions}
+              type="value"
+              placeholder="参数值"
+              valueType={valueType}
+              value={value}
+              name={0}
+              onChange={(v, lb) => {
+                setValue({
+                  ...v,
+                });
+                label[2] = { ...label[2], 0: lb };
+                setLabel([...label]);
+                valueEventChange(v);
+              }}
+            />
+            <ParamsDropdown
+              options={valueOptions}
+              metricsOptions={metricsOptions}
+              type="value"
+              placeholder="参数值"
+              valueType={valueType}
+              value={value}
+              name={1}
+              onChange={(v, lb) => {
+                setValue({
+                  ...v,
+                });
+                label[2] = { ...label[2], 1: lb };
+                setLabel([...label]);
+                valueEventChange(v);
+              }}
+            />
+          </>
+        ) : (
+          <ParamsDropdown
+            options={valueOptions}
+            metricsOptions={metricsOptions}
+            type="value"
+            placeholder="参数值"
+            valueType={valueType}
+            value={value}
+            onChange={(v, lb) => {
+              setValue({
+                ...v,
+              });
+              label[2] = { 0: lb };
+              setLabel([...label]);
+              valueEventChange(v);
+            }}
+          />
+        )}
+        <div
+          className={classNames('button-delete', { show: deleteVisible })}
+          onClick={props.onDelete}
+        >
           <CloseOutlined />
         </div>
       </div>
@@ -80,11 +302,11 @@ export default observer((props: ParamsItemProps) => {
             ]}
             isTree={false}
             type="type"
-            value="and"
+            value={props.data.type}
           />
         </div>
       ) : (
-        <div className="term-add" onClick={addItem}>
+        <div className="term-add" onClick={props.onAdd}>
           <PlusOutlined style={{ fontSize: 12, paddingRight: 4 }} />
           <span>条件</span>
         </div>
@@ -92,3 +314,5 @@ export default observer((props: ParamsItemProps) => {
     </div>
   );
 });
+
+export default ParasmItem;

+ 65 - 25
src/pages/rule-engine/Scene/Save/terms/term.tsx

@@ -1,34 +1,39 @@
-import { observer, Observer } from '@formily/react';
+import { observer } from '@formily/react';
 import { FormModel } from '@/pages/rule-engine/Scene/Save';
 import ParamsItem from './paramsItem';
-import { useState } from 'react';
+import { useState, useEffect } 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 { get, set } from 'lodash';
+import { get } from 'lodash';
+import { TermsModel } from './index';
 import './index.less';
 interface TermsProps {
   data: TermsType;
   pName: (number | string)[];
   name: number;
   isLast: boolean;
+  onValueChange: (data: TermsType) => void;
+  onLabelChange: (label: any) => void;
+  onDelete: () => void;
 }
 
 export default observer((props: TermsProps) => {
   const [deleteVisible, setDeleteVisible] = useState(false);
+  const [terms, setTerms] = useState<TermsType[]>([]);
 
-  const deleteTerms = () => {
-    const data = get(FormModel.branches, [...props.pName]);
-    const indexOf = data!.findIndex((item: TermsType) => item.key === props.data.key);
-    if (indexOf !== undefined && indexOf !== -1) {
-      data!.splice(indexOf, 1);
+  useEffect(() => {
+    if (props.data.terms) {
+      setTerms(props.data.terms);
+    } else {
+      setTerms([]);
     }
-    set(FormModel.branches!, [...props.pName], data);
-  };
+  }, [props.data.terms]);
 
   const addTerms = () => {
     const data = get(FormModel.branches, [...props.pName]);
+    FormModel.options!.terms[props.name].terms.push('');
     const key = `terms_${new Date().getTime()}`;
     const defaultValue = {
       type: 'and',
@@ -43,7 +48,6 @@ export default observer((props: TermsProps) => {
       key,
     };
     data?.push(defaultValue);
-    console.log(FormModel.branches);
   };
 
   return (
@@ -54,22 +58,58 @@ export default observer((props: TermsProps) => {
           onMouseOver={() => setDeleteVisible(true)}
           onMouseOut={() => setDeleteVisible(false)}
         >
-          <Observer>
-            {() =>
-              props.data.terms?.map((item, index) => (
-                <ParamsItem
-                  pName={[...props.pName, props.name]}
-                  name={index}
-                  data={item}
-                  key={item.key}
-                  isLast={index === props.data.terms!.length - 1}
-                />
-              ))
-            }
-          </Observer>
+          {terms.map((item, index) => (
+            <ParamsItem
+              pName={[...props.pName, props.name]}
+              name={index}
+              data={item}
+              key={item.key}
+              isLast={index === props.data.terms!.length - 1}
+              options={TermsModel.columnOptions}
+              onDelete={() => {
+                terms.splice(index, 1);
+                setTerms([...terms]);
+                props.onValueChange({
+                  terms: terms,
+                });
+              }}
+              onAdd={() => {
+                const key = `params_${new Date().getTime()}`;
+                terms.push({
+                  type: 'and',
+                  column: undefined,
+                  value: undefined,
+                  termType: undefined,
+                  key,
+                });
+                console.log('onAdd', terms);
+
+                setTerms([...terms]);
+                props.onValueChange({
+                  terms: terms,
+                });
+              }}
+              onValueChange={(data) => {
+                terms[index] = {
+                  ...terms[index],
+                  ...data,
+                };
+                setTerms([...terms]);
+                props.onValueChange({
+                  terms: terms,
+                });
+              }}
+              onLableChange={(options) => {
+                console.log(options, FormModel.options!.terms);
+                FormModel.options!.terms[props.name].terms[index] = options;
+                FormModel.options!.terms[props.name].termType =
+                  props.data.type === 'and' ? '并且' : '或者';
+              }}
+            />
+          ))}
           <div
             className={classNames('terms-params-delete', { show: deleteVisible })}
-            onClick={deleteTerms}
+            onClick={props.onDelete}
           >
             <CloseOutlined />
           </div>

+ 15 - 4
src/pages/rule-engine/Scene/Save/timer/index.tsx

@@ -5,6 +5,7 @@ import { Observer, observer } from '@formily/react';
 import { FormModel } from '@/pages/rule-engine/Scene/Save';
 import TimerTrigger from './TimerTrigger';
 import Action from '../action';
+import classNames from 'classnames';
 
 export default observer(() => {
   const [visible, setVisible] = useState<boolean>(false);
@@ -30,7 +31,7 @@ export default observer(() => {
       <div style={{ marginBottom: 16 }}>
         <Observer>
           {() => {
-            const label = handleLabel(FormModel.options);
+            const label = handleLabel(FormModel.trigger?.options);
             return (
               <AddButton
                 style={{ width: '100%' }}
@@ -38,21 +39,31 @@ export default observer(() => {
                   setVisible(true);
                 }}
               >
-                <div className="trigger-options-content">{label}</div>
+                <div
+                  className={classNames('trigger-options-content', {
+                    'is-add': !!Object.keys(FormModel.trigger?.options || {}).length,
+                  })}
+                >
+                  {label}
+                </div>
               </AddButton>
             );
           }}
         </Observer>
       </div>
       <div>
-        <Action />
+        <Observer>
+          {() => (
+            <Action thenOptions={FormModel.branches ? FormModel.branches[0].then : []} name={0} />
+          )}
+        </Observer>
       </div>
       {visible && (
         <TimerTrigger
           data={FormModel.trigger?.timer}
           save={(data, options) => {
             setVisible(false);
-            FormModel['options'] = options;
+            FormModel.trigger!['options'] = options;
             FormModel.trigger!.timer = data;
           }}
           close={() => {

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

@@ -114,7 +114,7 @@ export default observer((props: TriggerProps) => {
       setProductList(resp.result);
       if (FormModel.trigger && FormModel.trigger.device) {
         const productItem = resp.result.find(
-          (item: any) => item.id === FormModel.trigger!.device.productId,
+          (item: any) => item.id === FormModel.trigger!.device?.productId,
         );
 
         if (productItem) {

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

@@ -68,7 +68,7 @@ const Scene = () => {
           style={{ padding: 0 }}
           isPermission={permission.tigger}
           tooltip={{
-            title: record.state?.value === 'disabled' ? '未启用,不能手动触发' : '手动触发',
+            title: record.state?.value === 'disabled' ? '未启用,不能手动触发' : '',
           }}
           disabled={record.state?.value === 'disabled'}
           popConfirm={{
@@ -294,4 +294,5 @@ const Scene = () => {
     </PageContainer>
   );
 };
+
 export default Scene;

+ 9 - 2
src/pages/rule-engine/Scene/typings.d.ts

@@ -10,6 +10,13 @@ type Trigger = {
   device: Record<string, unknown>;
 };
 
+export enum ParallelEnum {
+  'parallel' = 'parallel',
+  'serial' = 'serial',
+}
+
+export type ParallelType = keyof typeof ParallelEnum;
+
 export enum Source {
   'manual' = 'manual',
   'metric' = 'metric',
@@ -162,7 +169,7 @@ export type TriggerType = {
 export interface TermsVale {
   source: keyof typeof Source;
   /** 手动输入值,source为 manual 时不能为空 */
-  value?: Record<string, any>;
+  value?: Record<string, any> | any[];
   /** 指标值,source为 metric 时不能为空 */
   metric?: Record<string, any>;
 }
@@ -239,7 +246,7 @@ export interface ActionsDeviceProps {
 
 export interface BranchesThen {
   parallel: boolean;
-  actions: ActionsType;
+  actions: ActionsType[];
   key?: string;
 }