Pārlūkot izejas kodu

feat: 优化触发条件回显

xieyonghong 3 gadi atpakaļ
vecāks
revīzija
e88273b678

+ 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 - 11
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,17 +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);
-        }}
-        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 (

+ 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) {
@@ -71,6 +85,7 @@ export default () => {
   }, [id]);
 
   const triggerRender = (type: string) => {
+    FormModel.trigger!.type = type;
     switch (type) {
       case 'device':
         return (
@@ -95,6 +110,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>
@@ -107,7 +132,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>
@@ -115,4 +140,4 @@ export default () => {
       </Card>
     </PageContainer>
   );
-};
+});

+ 50 - 37
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,42 +67,48 @@ 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]} />

+ 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>

+ 10 - 5
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,9 +31,7 @@ export default observer(() => {
       <div style={{ marginBottom: 16 }}>
         <Observer>
           {() => {
-            console.log(FormModel.options);
-
-            const label = handleLabel(FormModel.options);
+            const label = handleLabel(FormModel.trigger?.options);
             return (
               <AddButton
                 style={{ width: '100%' }}
@@ -40,7 +39,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>
             );
           }}
@@ -54,7 +59,7 @@ export default observer(() => {
           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) {

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

@@ -152,7 +152,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>;
 }