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

feat: 优化触发条件-否则

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

+ 98 - 0
src/pages/rule-engine/Scene/Save/components/Buttons/Dropdown.tsx

@@ -0,0 +1,98 @@
+import { useEffect, useMemo, useState } from 'react';
+import { Dropdown, Tree } from 'antd';
+import classNames from 'classnames';
+import styles from './index.less';
+
+type DropdownButtonOptions = {
+  title: string;
+  key: string;
+  children?: DropdownButtonOptions[];
+  [key: string]: any;
+};
+
+interface DropdownButtonProps {
+  className?: string;
+  placeholder?: string;
+  value?: string;
+  onChange?: (value?: string) => void;
+  options: DropdownButtonOptions[];
+  isTree?: boolean;
+  type: 'param' | 'termType' | 'value' | 'type';
+}
+
+const TypeStyle = {
+  param: styles.parameter,
+  termType: styles.termType,
+  value: styles.value,
+  type: styles.type,
+};
+
+export default (props: DropdownButtonProps) => {
+  const [myValue, setMyValue] = useState(props.value);
+  const [label, setLabel] = useState('');
+  const [loading, setLoading] = useState(false);
+
+  const typeClassName = TypeStyle[props.type];
+
+  const menuOnSelect = ({ key, item }: { key: string; item: any }) => {
+    props.onChange?.(key);
+    setMyValue(key);
+    setLabel(item.props.title);
+  };
+
+  const treeSelect = (selectedKeys: (string | number)[], e: any) => {
+    props.onChange?.(selectedKeys[0] as string);
+    setMyValue(selectedKeys[0] as string);
+    setLabel(e.node.title);
+  };
+
+  const menuOptions = {
+    selectedKeys: myValue ? [myValue] : [],
+    items: props.options.map((item) => ({ ...item, label: item.title })),
+    onClick: menuOnSelect,
+  };
+
+  const DropdownRender = useMemo(() => {
+    return (
+      <Tree
+        selectedKeys={myValue ? [myValue] : []}
+        onSelect={treeSelect}
+        treeData={props.options}
+      />
+    );
+  }, [props.options]);
+
+  const _options = !props.isTree ? { menu: menuOptions } : { dropdownRender: () => DropdownRender };
+
+  const findLable = (value: string, data: DropdownButtonOptions[]): boolean => {
+    let isLabel = false;
+    return data.some((item) => {
+      if (item.key === value) {
+        setLabel(item.title);
+        isLabel = true;
+      } else if (item.children) {
+        isLabel = findLable(value, item.children);
+      }
+      return isLabel;
+    });
+  };
+
+  useEffect(() => {
+    setMyValue(props.value);
+  }, [props.value]);
+
+  useEffect(() => {
+    if (myValue && !loading) {
+      findLable(myValue, props.options);
+      setLoading(true);
+    }
+  }, [props.options]);
+
+  return (
+    <Dropdown {..._options} trigger={['click']}>
+      <div className={classNames(styles['dropdown-button'], props.className, typeClassName)}>
+        {label || props.placeholder}
+      </div>
+    </Dropdown>
+  );
+};

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

@@ -22,3 +22,33 @@
     }
   }
 }
+
+.dropdown-button {
+  display: inline-block;
+  padding: 6px 8px;
+  border: 1px solid #d9d9d9;
+  border-radius: 8px;
+  cursor: pointer;
+}
+
+.parameter {
+  color: #00a4fe;
+  background-color: rgba(154, 219, 255, 0.3);
+  border-color: rgba(0, 164, 254, 0.3);
+}
+
+.termType {
+  color: #2f54eb;
+  background-color: rgba(163, 202, 255, 0.3);
+  border-color: rgba(47, 84, 235, 0.3);
+}
+
+.value {
+  color: #692ca7;
+  background-color: rgba(188, 125, 238, 0.1);
+  border-color: rgba(188, 125, 238, 0.5);
+}
+
+.type {
+  padding: 5px 10px;
+}

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

@@ -1,2 +1,3 @@
 import './index.less';
 export { default as AddButton } from './AddButton';
+export { default as DropdownButton } from './Dropdown';

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

@@ -12,14 +12,42 @@ import { service } from '@/pages/rule-engine/Scene';
 
 export const FormModel = observable<FormModelType>({
   actions: [],
-  terms: [
+  branches: [
     {
-      column: undefined,
-      value: undefined,
+      when: [
+        {
+          terms: [
+            {
+              column: undefined,
+              value: undefined,
+              key: 'params_1',
+            },
+          ],
+          type: 'and',
+          key: 'terms_1',
+        },
+      ],
+      key: 'branckes_1',
+      shakeLimit: {
+        enabled: false,
+        groupType: 'device',
+        time: 1,
+        threshold: 1,
+        alarmFirst: false,
+      },
+      then: [],
     },
     {
-      column: undefined,
-      value: undefined,
+      when: [],
+      key: 'branckes_2',
+      shakeLimit: {
+        enabled: false,
+        groupType: 'device',
+        time: 1,
+        threshold: 1,
+        alarmFirst: false,
+      },
+      then: [],
     },
   ],
 });

+ 104 - 0
src/pages/rule-engine/Scene/Save/terms/branchItem.tsx

@@ -0,0 +1,104 @@
+import { observer, Observer } from '@formily/react';
+import { useState } 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 Term from './term';
+import classNames from 'classnames';
+
+interface BranchesItemProps {
+  name: number;
+  data: ActionBranchesProps;
+  isFrist: boolean;
+}
+
+export default observer((props: BranchesItemProps) => {
+  const [deleteVisible, setDeleteVisible] = useState(false);
+
+  const deleteTerms = (index: number) => {
+    FormModel.branches?.splice(index, 1);
+  };
+
+  const addWhen = (index: number) => {
+    const lastBranch = FormModel.branches![index].when;
+    lastBranch.push({
+      terms: [
+        {
+          column: undefined,
+          value: undefined,
+          key: 'params_1',
+        },
+      ],
+      type: 'and',
+      key: 'terms_1',
+    });
+    // 增加下一个否则, '当' 排除
+    if (index > 0) {
+      FormModel.branches?.push({
+        when: [],
+        key: 'branch_' + FormModel.branches.length + 1,
+        shakeLimit: {
+          enabled: false,
+          groupType: 'device',
+          time: 1,
+          threshold: 1,
+          alarmFirst: false,
+        },
+        then: [],
+      });
+    }
+  };
+
+  return (
+    <div className="actions-terms-warp">
+      <div className="actions-terms-title">{props.isFrist ? '当' : '否则'}</div>
+      <div
+        className={classNames('actions-terms-options', { border: !props.isFrist })}
+        onMouseOver={() => setDeleteVisible(true)}
+        onMouseOut={() => setDeleteVisible(false)}
+      >
+        {!props.isFrist && props.data.when?.length ? (
+          <div
+            className={classNames('terms-params-delete denger', { show: deleteVisible })}
+            onClick={() => deleteTerms(props.name)}
+          >
+            <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>
+        </div>
+        <div className="actions-branchs"></div>
+      </div>
+    </div>
+  );
+});

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

@@ -8,7 +8,7 @@
   color: #999;
   line-height: 20px;
   text-align: center;
-  background-color: rgba(#f1f1f1, 0.8);
+  background-color: #f1f1f1;
   border-radius: 50%;
   cursor: pointer;
 
@@ -17,22 +17,98 @@
   }
 
   &:hover {
-    background-color: #f1f1f1;
+    background-color: #f3f3f3;
+  }
+}
+
+.actions-terms {
+  .actions-terms-warp {
+    display: flex;
+
+    &:not(:last-child) {
+      margin-bottom: 24px;
+    }
+
+    .actions-terms-title {
+      width: 40px;
+      padding-top: 16px;
+      color: #6968be;
+      font-weight: 800;
+      font-size: 16px;
+    }
+
+    .actions-terms-options {
+      position: relative;
+      display: flex;
+      flex-grow: 1;
+      width: 0;
+
+      &.border {
+        padding: 18px;
+        border: 1px dashed #999;
+        border-radius: 2px;
+      }
+
+      .actions-terms-list {
+        display: flex;
+        flex-wrap: wrap;
+        row-gap: 16px;
+      }
+    }
+  }
+
+  .terms-params-delete {
+    .deleteBtn();
+
+    &.denger {
+      color: #e50012;
+      background-color: rgba(229, 0, 18, 0.1);
+    }
   }
 }
 
 .terms-params {
   display: inline-block;
 
+  // &:not(:first-child) {
+  //   margin-bottom: 16px;
+  // }
+
+  .terms-params-warp {
+    display: flex;
+    align-items: center;
+  }
+
   .terms-params-content {
     position: relative;
     display: flex;
+    flex-wrap: wrap;
     padding: 8px;
     background-color: #fafafa;
+    row-gap: 16px;
   }
 
-  .terms-params-delete {
-    .deleteBtn();
+  .terms-add {
+    display: inline-block;
+    width: 66px;
+    margin-left: 16px;
+    padding: 2px 8px;
+    color: rgba(0, 0, 0, 0.3);
+    background: #fff;
+    border: 1px dashed rgba(0, 0, 0, 0.3);
+    border-radius: 30px;
+    cursor: pointer;
+  }
+
+  .term-type-warp {
+    display: inline-block;
+    width: 60px;
+    margin: 0 16px;
+    .term-type {
+      padding-top: 4px;
+      padding-bottom: 4px;
+      border-radius: 2px;
+    }
   }
 }
 
@@ -52,46 +128,20 @@
     border: 1px solid rgba(0, 0, 0, 0.1);
     border-radius: 2px;
 
-    .params-button {
-      &.parameter {
-        color: #00a4fe;
-        background-color: rgba(154, 219, 255, 0.3);
-        border-color: rgba(0, 164, 254, 0.3);
-      }
-
-      &.termType {
-        color: #2f54eb;
-        background-color: rgba(163, 202, 255, 0.3);
-        border-color: rgba(47, 84, 235, 0.3);
-      }
-
-      &.termsValue {
-        color: #692ca7;
-        background-color: rgba(188, 125, 238, 0.1);
-        border-color: rgba(188, 125, 238, 0.5);
-      }
-    }
-
     .button-delete {
       .deleteBtn();
     }
   }
 
-  .term-type-warp {
-    display: inline-block;
-    margin: 0 16px;
-    .term-type {
-      border-radius: 2px;
-    }
-  }
-
   .term-add {
     display: inline-block;
+    width: 66px;
     margin-left: 16px;
     padding: 4px 8px;
     color: #333;
     background: #e8e8e8;
     border: 1px solid #f0f0f0;
     border-radius: 30px;
+    cursor: pointer;
   }
 }

+ 10 - 4
src/pages/rule-engine/Scene/Save/terms/index.tsx

@@ -1,14 +1,13 @@
 import { useEffect } from 'react';
 import { TitleComponent } from '@/components';
-import { observer } from '@formily/react';
+import { observer, Observer } from '@formily/react';
 import { FormModel } from '@/pages/rule-engine/Scene/Save';
-import Term from './term';
+import BranchItem from './branchItem';
 
 export default observer(() => {
   const queryColumn = () => {};
 
   useEffect(() => {
-    console.log('terms', FormModel.trigger?.device);
     if (FormModel.trigger?.device) {
       queryColumn();
     }
@@ -17,7 +16,14 @@ export default observer(() => {
   return (
     <div className="actions-terms">
       <TitleComponent style={{ fontSize: 14 }} data="触发条件" />
-      <Term />
+      <Observer>
+        {() =>
+          FormModel.branches?.map((item, index) => {
+            const isFrist = index === 0;
+            return <BranchItem data={item} isFrist={isFrist} name={index} />;
+          })
+        }
+      </Observer>
     </div>
   );
 });

+ 53 - 19
src/pages/rule-engine/Scene/Save/terms/paramsItem.tsx

@@ -1,20 +1,46 @@
 import { useState } from 'react';
 import type { TermsType } from '@/pages/rule-engine/Scene/typings';
-import { Popover } from 'antd';
+import { DropdownButton } 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';
 
 interface ParamsItemProps {
   data: TermsType;
+  pName: (number | string)[];
   name: number;
   isLast: boolean;
 }
 
-export default (props: ParamsItemProps) => {
+export default observer((props: ParamsItemProps) => {
   const [deleteVisible, setDeleteVisible] = useState(false);
+  const [paramOptions] = useState([]);
+  const [termTypeOptions] = useState([]);
+  const [valueOptions] = useState([]);
 
-  const deleteItem = () => {};
+  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);
+    }
+    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,
+    });
+  };
 
   return (
     <div className="terms-params-item">
@@ -23,31 +49,39 @@ export default (props: ParamsItemProps) => {
         onMouseOver={() => setDeleteVisible(true)}
         onMouseOut={() => setDeleteVisible(false)}
       >
-        <Popover trigger={'click'} content={<span>1231</span>}>
-          <div className="params-button parameter">请选择参数</div>
-        </Popover>
-        <Popover trigger={'click'} content={<span>1231</span>}>
-          <div className="params-button termType">操作符</div>
-        </Popover>
-        <Popover trigger={'click'} content={<span>1231</span>}>
-          <div className="params-button termsValue">参数值</div>
-        </Popover>
-        <div className={classNames('button-delete', { show: deleteVisible })}>
+        <DropdownButton
+          options={paramOptions}
+          type="param"
+          placeholder="请选择参数"
+        ></DropdownButton>
+        <DropdownButton
+          options={termTypeOptions}
+          type="termType"
+          placeholder="操作符"
+        ></DropdownButton>
+        <DropdownButton options={valueOptions} type="value" placeholder="参数值"></DropdownButton>
+        <div className={classNames('button-delete', { show: deleteVisible })} onClick={deleteItem}>
           <CloseOutlined />
         </div>
       </div>
       {!props.isLast ? (
-        <div className="term-type-warp" onClick={deleteItem}>
-          <Popover trigger={'click'} content={<span>1231</span>}>
-            <div className="params-button term-type">参数值</div>
-          </Popover>
+        <div className="term-type-warp">
+          <DropdownButton
+            options={[
+              { title: '并且', key: 'and' },
+              { title: '或者', key: 'or' },
+            ]}
+            isTree={false}
+            type="type"
+            value="and"
+          ></DropdownButton>
         </div>
       ) : (
-        <div className="term-add">
+        <div className="term-add" onClick={addItem}>
           <PlusOutlined style={{ fontSize: 12, paddingRight: 4 }} />
           <span>条件</span>
         </div>
       )}
     </div>
   );
-};
+});

+ 85 - 24
src/pages/rule-engine/Scene/Save/terms/term.tsx

@@ -1,37 +1,98 @@
-import { Observer } from '@formily/react';
+import { observer, Observer } from '@formily/react';
 import { FormModel } from '@/pages/rule-engine/Scene/Save';
 import ParamsItem from './paramsItem';
 import { useState } from 'react';
-import { CloseOutlined } from '@ant-design/icons';
+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 './index.less';
+interface TermsProps {
+  data: TermsType;
+  pName: (number | string)[];
+  name: number;
+  isLast: boolean;
+}
 
-export default () => {
+export default observer((props: TermsProps) => {
   const [deleteVisible, setDeleteVisible] = useState(false);
 
+  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);
+    }
+    set(FormModel.branches!, [...props.pName], data);
+  };
+
+  const addTerms = () => {
+    const data = get(FormModel.branches, [...props.pName]);
+    const key = 'terms_' + new Date().getTime();
+    const defaultValue = {
+      type: 'and',
+      terms: [
+        {
+          column: undefined,
+          value: undefined,
+          key: 'params_1',
+          type: 'and',
+        },
+      ],
+      key,
+    };
+    data?.push(defaultValue);
+    console.log(FormModel.branches);
+  };
+
   return (
-    <div
-      className="terms-params"
-      onMouseOver={() => setDeleteVisible(true)}
-      onMouseOut={() => setDeleteVisible(false)}
-    >
-      <div className="terms-params-content">
-        <Observer>
-          {() =>
-            FormModel.terms?.map((item, index) => (
-              <ParamsItem
-                name={index}
-                data={item}
-                key={item.key}
-                isLast={index === FormModel.terms!.length - 1}
-              />
-            ))
-          }
-        </Observer>
-        <div className={classNames('terms-params-delete', { show: deleteVisible })}>
-          <CloseOutlined />
+    <div className="terms-params">
+      <div className="terms-params-warp">
+        <div
+          className="terms-params-content"
+          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>
+          <div
+            className={classNames('terms-params-delete', { show: deleteVisible })}
+            onClick={deleteTerms}
+          >
+            <CloseOutlined />
+          </div>
         </div>
+        {!props.isLast ? (
+          <div className="term-type-warp">
+            <DropdownButton
+              options={[
+                { title: '并且', key: 'and' },
+                { title: '或者', key: 'or' },
+              ]}
+              isTree={false}
+              type="type"
+              value={props.data.type}
+            ></DropdownButton>
+          </div>
+        ) : (
+          <div className="terms-add" onClick={addTerms}>
+            <PlusOutlined style={{ fontSize: 12, paddingRight: 4 }} />
+            <span>分组</span>
+          </div>
+        )}
       </div>
     </div>
   );
-};
+});

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

@@ -230,12 +230,14 @@ export interface ActionsDeviceProps {
 export interface BranchesThen {
   parallel: boolean;
   actions: ActionsType;
+  key?: string;
 }
 
 export interface ActionBranchesProps {
   when: TermsType[];
   shakeLimit: ShakeLimitType;
   then: BranchesThen[];
+  key?: string;
 }
 
 export interface ActionsType {