Browse Source

feat(merge): merge xyh

Next xyh
Lind 3 years atrás
parent
commit
719a6b70d4
36 changed files with 2516 additions and 299 deletions
  1. 2 2
      src/components/ProTableCard/CardItems/scene.tsx
  2. 30 7
      src/pages/device/Instance/Detail/Functions/form.tsx
  3. 1 0
      src/pages/device/Instance/index.tsx
  4. 4 1
      src/pages/device/Instance/service.ts
  5. 1 1
      src/pages/link/AccessConfig/Detail/Provider/index.less
  6. 139 0
      src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx
  7. 1 0
      src/pages/rule-engine/Scene/Save/action/VariableItems/email.tsx
  8. 4 0
      src/pages/rule-engine/Scene/Save/action/VariableItems/index.ts
  9. 63 0
      src/pages/rule-engine/Scene/Save/action/VariableItems/org.tsx
  10. 50 0
      src/pages/rule-engine/Scene/Save/action/VariableItems/tag.tsx
  11. 232 0
      src/pages/rule-engine/Scene/Save/action/VariableItems/user.tsx
  12. 69 46
      src/pages/rule-engine/Scene/Save/action/action.tsx
  13. 152 0
      src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx
  14. 180 0
      src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx
  15. 151 0
      src/pages/rule-engine/Scene/Save/action/device/functionCall.tsx
  16. 182 0
      src/pages/rule-engine/Scene/Save/action/device/index.tsx
  17. 24 0
      src/pages/rule-engine/Scene/Save/action/device/readProperty.tsx
  18. 13 0
      src/pages/rule-engine/Scene/Save/action/device/service.ts
  19. 0 0
      src/pages/rule-engine/Scene/Save/action/device/sourceItem.tsx
  20. 263 0
      src/pages/rule-engine/Scene/Save/action/device/tagModal.tsx
  21. 41 133
      src/pages/rule-engine/Scene/Save/action/messageContent.tsx
  22. 40 0
      src/pages/rule-engine/Scene/Save/action/service.ts
  23. 28 0
      src/pages/rule-engine/Scene/Save/components/DatePickerFormat/index.tsx
  24. 53 0
      src/pages/rule-engine/Scene/Save/components/InputNumber.tsx
  25. 1 1
      src/pages/rule-engine/Scene/Save/components/InputUpload/index.tsx
  26. 7 2
      src/pages/rule-engine/Scene/Save/components/TimeSelect/index.tsx
  27. 179 34
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/index.tsx
  28. 5 0
      src/pages/rule-engine/Scene/Save/components/TriggerWay/index.tsx
  29. 2 0
      src/pages/rule-engine/Scene/Save/components/index.ts
  30. 201 47
      src/pages/rule-engine/Scene/Save/index.tsx
  31. 262 6
      src/pages/rule-engine/Scene/Save/trigger/index.tsx
  32. 81 0
      src/pages/rule-engine/Scene/Save/trigger/operation.tsx
  33. 27 0
      src/pages/rule-engine/Scene/Save/trigger/service.ts
  34. 1 1
      src/pages/rule-engine/Scene/TriggerTerm/index.tsx
  35. 26 17
      src/pages/rule-engine/Scene/index.tsx
  36. 1 1
      src/pages/system/Department/Assets/product/bind.tsx

+ 2 - 2
src/components/ProTableCard/CardItems/scene.tsx

@@ -19,8 +19,8 @@ export default (props: DeviceCardProps) => {
       status={props.state.value}
       statusText={props.state.text}
       statusNames={{
-        online: StatusColorEnum.processing,
-        offline: StatusColorEnum.error,
+        started: StatusColorEnum.processing,
+        disable: StatusColorEnum.error,
         notActive: StatusColorEnum.warning,
       }}
     >

+ 30 - 7
src/pages/device/Instance/Detail/Functions/form.tsx

@@ -27,8 +27,20 @@ export default (props: FunctionProps) => {
   const formRef = useRef<ProFormInstance<any>>();
   const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
 
-  const getItemNode = (type: string) => {
+  const getItemNode = (record: any) => {
+    const type = record.type;
+    const name = record.name;
+
     switch (type) {
+      case 'enum':
+        return (
+          <Select
+            style={{ width: '100%', textAlign: 'left' }}
+            options={record.options}
+            fieldNames={{ label: 'text', value: 'value' }}
+            placeholder={'请选择' + name}
+          />
+        );
       case 'boolean':
         return (
           <Select
@@ -37,19 +49,28 @@ export default (props: FunctionProps) => {
               { label: 'true', value: true },
               { label: 'false', value: false },
             ]}
-            placeholder={'请选择'}
+            placeholder={'请选择' + name}
           />
         );
       case 'int':
       case 'long':
       case 'float':
       case 'double':
-        return <InputNumber style={{ width: '100%' }} placeholder={'请输入'} />;
+        return <InputNumber style={{ width: '100%' }} placeholder={'请输入' + name} />;
       case 'date':
-        // @ts-ignore
-        return <DatePicker style={{ width: '100%' }} />;
+        return (
+          <>
+            {
+              // @ts-ignore
+              <DatePicker
+                format={record.format || 'YYYY-MM-DD HH:mm:ss'}
+                style={{ width: '100%' }}
+              />
+            }
+          </>
+        );
       default:
-        return <Input placeholder={'请输入'} />;
+        return <Input placeholder={'请输入' + name} />;
     }
   };
 
@@ -75,7 +96,7 @@ export default (props: FunctionProps) => {
       align: 'center',
       width: 260,
       renderFormItem: (_, row: any) => {
-        return getItemNode(row.record.type);
+        return getItemNode(row.record);
       },
     },
   ];
@@ -89,6 +110,8 @@ export default (props: FunctionProps) => {
         id: datum.id,
         name: datum.name,
         type: datum.valueType ? datum.valueType.type : '-',
+        format: datum.valueType ? datum.valueType.format : undefined,
+        options: datum.valueType ? datum.valueType.elements : undefined,
         value: undefined,
       });
     }

+ 1 - 0
src/pages/device/Instance/index.tsx

@@ -84,6 +84,7 @@ const Instance = () => {
           value: location.state[key],
         });
       });
+      console.log(_terms);
       setJumpParams([
         {
           terms: _terms,

+ 4 - 1
src/pages/device/Instance/service.ts

@@ -10,7 +10,10 @@ class Service extends BaseService<DeviceInstance> {
 
   // 查询产品列表
   public getProductList = (params?: any) =>
-    request(`/${SystemConst.API_BASE}/device/product/_query/no-paging`, { method: 'GET', params });
+    request(`/${SystemConst.API_BASE}/device/product/_query/no-paging?paging=false`, {
+      method: 'GET',
+      params,
+    });
 
   // 批量删除设备
   public batchDeleteDevice = (params: any) =>

+ 1 - 1
src/pages/link/AccessConfig/Detail/Provider/index.less

@@ -29,7 +29,7 @@
 }
 
 .title {
-  width: '100%';
+  width: 100%;
   margin-bottom: 10px;
   overflow: hidden;
   font-weight: 800;

+ 139 - 0
src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx

@@ -0,0 +1,139 @@
+import { DatePicker, Input, InputNumber, Select } from 'antd';
+import { useCallback, useEffect, useState } from 'react';
+import { useRequest } from 'umi';
+import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
+import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
+import moment from 'moment';
+
+type ChangeType = {
+  source?: string;
+  value?: string;
+  upperKey?: string;
+};
+
+interface BuiltInProps {
+  value?: ChangeType;
+  data?: any;
+  type?: string;
+  onChange?: (value: ChangeType) => void;
+}
+
+export default (props: BuiltInProps) => {
+  const [source, setSource] = useState(props.value?.source);
+  const [value, setValue] = useState(props.value?.value);
+  const [upperKey, setUpperKey] = useState(props.value?.upperKey);
+
+  const [builtInList, setBuiltInList] = useState([]);
+
+  const { run: getBuiltInList } = useRequest(queryBuiltInParams, {
+    manual: true,
+    formatResult: (res) => res.result,
+    onSuccess: (res) => {
+      setBuiltInList(res);
+    },
+  });
+
+  useEffect(() => {
+    if (source === 'upper') {
+      getBuiltInList({
+        trigger: { type: props.type },
+      });
+    }
+  }, [source, props.type]);
+
+  useEffect(() => {
+    setSource(props.value?.source);
+    setValue(props.value?.value);
+    setUpperKey(props.value?.upperKey);
+  }, [props.value]);
+
+  const onChange = (_source: string = 'fixed', _value?: any, _upperKey?: string) => {
+    const obj: ChangeType = {
+      source: _source,
+    };
+    if (_value) {
+      obj.value = _value;
+    }
+    if (_upperKey) {
+      obj.upperKey = _upperKey;
+    }
+
+    if (props.onChange) {
+      props.onChange(obj);
+    }
+  };
+
+  const itemOnChange = useCallback(
+    (_value: any) => {
+      onChange(source, _value);
+    },
+    [source],
+  );
+
+  const inputNodeByType = useCallback(
+    (data: any) => {
+      switch (data.type) {
+        case 'date':
+          return (
+            // @ts-ignore
+            <DatePicker
+              value={value ? moment(value) : undefined}
+              style={{ width: '100%' }}
+              format={data.format || 'YYYY-MM-DD HH:mm:ss'}
+              onChange={(date) => {
+                itemOnChange(date?.format(data.format || 'YYYY-MM-DD HH:mm:ss'));
+              }}
+            />
+          );
+        case 'number':
+          return (
+            <InputNumber
+              value={value}
+              placeholder={`请输入${data.name}`}
+              style={{ width: '100%' }}
+              onChange={itemOnChange}
+            />
+          );
+        default:
+          return (
+            <Input
+              value={value}
+              placeholder={`请输入${data.name}`}
+              onChange={(e) => itemOnChange(e.target.value)}
+            />
+          );
+      }
+    },
+    [value],
+  );
+
+  return (
+    <ItemGroup>
+      <Select
+        value={source}
+        options={[
+          { label: '手动输入', value: 'fixed' },
+          { label: '内置参数', value: 'upper' },
+        ]}
+        style={{ width: 120 }}
+        onChange={(key) => {
+          setSource(key);
+          onChange(key, undefined, undefined);
+        }}
+      ></Select>
+      {source === 'upper' ? (
+        <Select
+          value={upperKey}
+          options={builtInList}
+          onChange={(key) => {
+            onChange(source, undefined, key);
+          }}
+          fieldNames={{ label: 'name', value: 'id' }}
+          placeholder={'请选择参数'}
+        />
+      ) : (
+        <div>{inputNodeByType(props.data)}</div>
+      )}
+    </ItemGroup>
+  );
+};

+ 1 - 0
src/pages/rule-engine/Scene/Save/action/VariableItems/email.tsx

@@ -0,0 +1 @@
+export default () => {};

+ 4 - 0
src/pages/rule-engine/Scene/Save/action/VariableItems/index.ts

@@ -0,0 +1,4 @@
+export { default as UserList } from './user';
+export { default as OrgList } from './org';
+export { default as BuiltIn } from './builtIn';
+export { default as TagSelect } from './tag';

+ 63 - 0
src/pages/rule-engine/Scene/Save/action/VariableItems/org.tsx

@@ -0,0 +1,63 @@
+import { TreeSelect } from 'antd';
+import { useEffect, useState } from 'react';
+import {
+  queryDingTalkDepartments,
+  queryWechatDepartments,
+} from '@/pages/rule-engine/Scene/Save/action/service';
+
+type ChangeType = {
+  value?: string[];
+};
+
+interface OrgProps {
+  value?: ChangeType;
+  data?: any;
+  notifyType: string;
+  configId: string;
+  onChange?: (value: ChangeType) => void;
+}
+
+export default (props: OrgProps) => {
+  const [keys, setKeys] = useState<any[]>(props.value?.value || []);
+  const [departmentTree, setDepartmentTree] = useState([]);
+
+  const getDepartment = async (id: string) => {
+    if (props.notifyType === 'dingTalk') {
+      const resp = await queryDingTalkDepartments(id);
+      if (resp.status === 200) {
+        setDepartmentTree(resp.result);
+      }
+    } else {
+      const resp = await queryWechatDepartments(id);
+      if (resp.status === 200) {
+        setDepartmentTree(resp.result);
+      }
+    }
+  };
+
+  useEffect(() => {
+    getDepartment(props.configId);
+  }, []);
+
+  useEffect(() => {
+    setKeys(props.value?.value || []);
+  }, [props.value]);
+
+  return (
+    <TreeSelect
+      value={keys}
+      listHeight={200}
+      treeData={departmentTree}
+      fieldNames={{
+        label: 'name',
+        value: 'id',
+      }}
+      onChange={(key) => {
+        if (props.onChange) {
+          props.onChange({ value: key });
+        }
+      }}
+      placeholder={'请选择部门'}
+    />
+  );
+};

+ 50 - 0
src/pages/rule-engine/Scene/Save/action/VariableItems/tag.tsx

@@ -0,0 +1,50 @@
+import { Select } from 'antd';
+import { useEffect, useState } from 'react';
+import { queryTag } from '@/pages/rule-engine/Scene/Save/action/service';
+
+interface TagSelectProps {
+  configId?: string;
+  value?: string;
+  onChange?: (value: string) => void;
+}
+
+export default (props: TagSelectProps) => {
+  const [value, setValue] = useState<string | undefined>(props.value);
+  const [options, setOptions] = useState([]);
+
+  useEffect(() => {
+    if (props.configId) {
+      queryTag(props.configId).then((res) => {
+        if (res.status === 200) {
+          setOptions(res.result);
+        } else {
+          setOptions([]);
+        }
+      });
+    } else {
+      setOptions([]);
+    }
+  }, [props.configId]);
+
+  useEffect(() => {
+    setValue(props.value);
+  }, [props.value]);
+
+  return (
+    <Select
+      value={value}
+      placeholder={'请选择标签'}
+      options={options}
+      fieldNames={{
+        label: 'name',
+        value: 'id',
+      }}
+      style={{ width: '100%' }}
+      onChange={(key) => {
+        if (props.onChange) {
+          props.onChange(key);
+        }
+      }}
+    />
+  );
+};

+ 232 - 0
src/pages/rule-engine/Scene/Save/action/VariableItems/user.tsx

@@ -0,0 +1,232 @@
+// 收信人
+import { useEffect, useState } from 'react';
+import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
+import { Select } from 'antd';
+import {
+  queryDingTalkUsers,
+  queryPlatformUsers,
+  queryRelationUsers,
+  queryWechatUsers,
+} from '@/pages/rule-engine/Scene/Save/action/service';
+
+type ChangeType = {
+  source?: string;
+  value?: string;
+  relation?: any;
+};
+
+interface UserProps {
+  notifyType: string;
+  configId: string;
+  value?: ChangeType;
+  type?: string;
+  onChange?: (value: ChangeType) => void;
+}
+
+export default (props: UserProps) => {
+  const [source, setSource] = useState(props.value?.source);
+  const [value, setValue] = useState<string | undefined>('');
+  const [userList, setUserList] = useState({ platform: [], relation: [] });
+  const [relationList, setRelationList] = useState([]);
+
+  useEffect(() => {
+    setSource(props.value?.source);
+    if (props.value?.source === 'relation') {
+      const relation = props.value?.relation;
+      console.log(relation);
+      if (relation) {
+        if (relation.objectId) {
+          // 平台用户
+          setValue(relation.objectId);
+        } else {
+          // 关系用户
+          setValue(relation.related?.relation);
+        }
+      }
+    } else {
+      setValue(props.value?.value);
+    }
+  }, [props.value]);
+
+  const getPlatformUser = async () => {
+    const _userList: any = {
+      platform: [],
+      relation: [],
+    };
+    const resp1 = await queryPlatformUsers();
+    if (resp1.status === 200) {
+      _userList.platform = resp1.result.map((item: any) => ({ label: item.name, value: item.id }));
+    }
+
+    const resp2 = await queryRelationUsers();
+    if (resp2.status === 200) {
+      _userList.relation = resp2.result.map((item: any) => ({
+        label: item.name,
+        value: item.relation,
+      }));
+    }
+
+    setUserList(_userList);
+  };
+
+  const getRelationUsers = async (notifyType: string, configId: string) => {
+    if (notifyType === 'dingTalk') {
+      const resp = await queryDingTalkUsers(configId);
+      if (resp.status === 200) {
+        setRelationList(resp.result);
+      }
+    } else {
+      const resp = await queryWechatUsers(configId);
+      if (resp.status === 200) {
+        setRelationList(resp.result);
+      }
+    }
+  };
+
+  useEffect(() => {
+    if (source === 'fixed') {
+      // 钉钉,微信用户
+      getRelationUsers(props.notifyType, props.configId);
+    } else {
+      getPlatformUser();
+    }
+  }, [source, props.notifyType]);
+
+  const options = [
+    { label: '平台用户', value: 'relation' },
+    { label: props.notifyType === 'dingTalk' ? '钉钉用户' : '微信用户', value: 'fixed' },
+  ];
+
+  /**
+   * 收信人-平台用户格式
+   * {
+   *   source: 'relation',
+   *   relation: {
+   *     related: {
+   *       objectType: 'user',
+   *       relation: 'userId'
+   *     }
+   *   }
+   * }
+   * 收信人-平台-关系用户格式
+   * {
+   *   source: 'relation',
+   *   relation: {
+   *     objectType: 'device',
+   *     objectSource: {
+   *       source: 'upper',
+   *       upperKey: 'deviceId'
+   *     },
+   *     related: {
+   *       objectType: 'user',
+   *       relation: 'userId'
+   *     }
+   *   }
+   * }
+   * 收信人-钉钉/微信用户
+   * {
+   *   source: 'relation',
+   *   value: 'userId'
+   * }
+   * @param _source
+   * @param _value
+   * @param type
+   */
+  const onchange = (_source: string = 'fixed', _value?: string, isRelation?: boolean) => {
+    const obj: any = {
+      source: _source,
+    };
+    if (_source === 'relation') {
+      if (isRelation) {
+        obj.relation = {
+          objectType: 'device',
+          objectSource: {
+            source: 'upper',
+            upperKey: 'deviceId',
+          },
+          related: {
+            objectType: 'user',
+            relation: _value,
+          },
+        };
+      } else {
+        obj.relation = {
+          objectType: 'user',
+          objectId: _value,
+        };
+      }
+    } else {
+      obj.value = _value;
+    }
+    console.log(obj);
+    if (props.onChange) {
+      props.onChange(obj);
+    }
+  };
+
+  const filterOption = (input: string, option: any) => {
+    return option.children ? option.children.toLowerCase().includes(input.toLowerCase()) : false;
+  };
+
+  return (
+    <ItemGroup>
+      <Select
+        value={source}
+        options={options}
+        style={{ width: 120 }}
+        onChange={(key) => {
+          setSource(key);
+          onchange(key, undefined);
+        }}
+      />
+      {source === 'relation' ? (
+        <Select
+          showSearch
+          value={value}
+          onChange={(key, node) => {
+            setValue(key);
+            onchange(source, key, node.isRelation);
+          }}
+          placeholder={'请选择收信人'}
+          listHeight={200}
+          filterOption={filterOption}
+        >
+          {userList.platform.length ? (
+            <Select.OptGroup label={'平台用户'}>
+              {userList.platform.map((item: any) => (
+                <Select.Option value={item.value} isRelation={false}>
+                  {item.label}
+                </Select.Option>
+              ))}
+            </Select.OptGroup>
+          ) : null}
+          {userList.relation.length ? (
+            <Select.OptGroup label={'关系用户'}>
+              {userList.relation.map((item: any) => (
+                <Select.Option value={item.value} isRelation={true}>
+                  {item.label}
+                </Select.Option>
+              ))}
+            </Select.OptGroup>
+          ) : null}
+        </Select>
+      ) : (
+        <Select
+          showSearch
+          value={value}
+          options={relationList}
+          listHeight={200}
+          onChange={(key) => {
+            setValue(key);
+            onchange(source, key);
+          }}
+          fieldNames={{ label: 'name', value: 'id' }}
+          placeholder={'请选择收信人'}
+          filterOption={(input: string, option: any) => {
+            return option.name ? option.name.toLowerCase().includes(input.toLowerCase()) : false;
+          }}
+        ></Select>
+      )}
+    </ItemGroup>
+  );
+};

+ 69 - 46
src/pages/rule-engine/Scene/Save/action/action.tsx

@@ -1,4 +1,4 @@
-import { Button, InputNumber, Select, Form } from 'antd';
+import { Button, Select, Form } from 'antd';
 import type { FormInstance } from 'antd';
 import { useEffect, useState } from 'react';
 import { useRequest } from 'umi';
@@ -9,19 +9,32 @@ import {
   queryMessageTemplateDetail,
 } from './service';
 import MessageContent from './messageContent';
+import DeviceSelect, { MessageTypeEnum } from './device';
+import WriteProperty from './device/WriteProperty';
+import ReadProperty from './device/readProperty';
+import FunctionCall from './device/functionCall';
+import { InputNumber } from '../components';
 
 interface ActionProps {
   restField: any;
   name: number;
   form: FormInstance;
   title?: string;
+  triggerType: string;
   onRemove: () => void;
 }
 
 const ActionItem = (props: ActionProps) => {
   const { name } = props;
   const [type1, setType1] = useState('');
+  // 消息通知
+  const [notifyType, setNotifyType] = useState('');
+  const [configId, setConfigId] = useState('');
   const [templateData, setTemplateData] = useState<any>(undefined);
+  // 设备输出
+  const [deviceMessageType, setDeviceMessageType] = useState('WRITE_PROPERTY');
+  const [properties, setProperties] = useState([]); // 物模型-属性
+  const [functionList, setFunctionList] = useState([]); // 物模型-功能
 
   const { data: messageType, run: queryMessageTypes } = useRequest(queryMessageType, {
     manual: true,
@@ -56,6 +69,7 @@ const ActionItem = (props: ActionProps) => {
           style={{ width: 140 }}
           onChange={async (key: string) => {
             setTemplateData(undefined);
+            setNotifyType(key);
             props.form.resetFields([['actions', name, 'notify', 'notifierId']]);
             props.form.resetFields([['actions', name, 'notify', 'templateId']]);
             await queryMessageConfigs({
@@ -69,11 +83,16 @@ const ActionItem = (props: ActionProps) => {
           options={messageConfig}
           loading={messageConfigLoading}
           fieldNames={{ value: 'id', label: 'name' }}
-          onChange={async (key: string) => {
+          onChange={async (key: string, node: any) => {
+            setConfigId(key);
             setTemplateData(undefined);
             props.form.resetFields([['actions', name, 'notify', 'templateId']]);
+
             await queryMessageTemplates({
-              terms: [{ column: 'configId', value: key }],
+              terms: [
+                { column: 'type', value: notifyType },
+                { column: 'provider', value: node.provider },
+              ],
             });
           }}
           style={{ width: 160 }}
@@ -98,48 +117,12 @@ const ActionItem = (props: ActionProps) => {
     </>
   );
 
-  const DeviceNodes = (
-    <>
-      <Select options={[]} placeholder={'请选择产品'} style={{ width: 220 }} />
-      <Select
-        defaultValue={'1'}
-        options={[
-          { label: '固定设备', value: '1' },
-          { label: '按标签', value: '2' },
-          { label: '按关系', value: '3' },
-        ]}
-        style={{ width: 120 }}
-      />
-      <Select options={[]} placeholder={'请选择'} style={{ width: 180 }} />
-      <Select
-        defaultValue={'1'}
-        options={[
-          { label: '设置属性', value: '1' },
-          { label: '功能调用', value: '2' },
-          { label: '读取属性', value: '3' },
-        ]}
-        style={{ width: 120 }}
-      />
-    </>
-  );
-
   useEffect(() => {
-    if (type1 === 'message') {
+    if (type1 === 'notify') {
       queryMessageTypes();
     }
   }, [type1]);
 
-  const TimeTypeAfter = (
-    <Select
-      defaultValue={'second'}
-      options={[
-        { label: '秒', value: 'second' },
-        { label: '分', value: 'minute' },
-        { label: '小时', value: 'hour' },
-      ]}
-    />
-  );
-
   return (
     <div className={'actions-item'}>
       <div className={'actions-item-title'}>
@@ -152,7 +135,7 @@ const ActionItem = (props: ActionProps) => {
         <Form.Item {...props.restField} name={[name, 'executor']}>
           <Select
             options={[
-              { label: '消息通知', value: 'message' },
+              { label: '消息通知', value: 'notify' },
               { label: '设备输出', value: 'device' },
               { label: '延迟执行', value: 'delay' },
             ]}
@@ -163,14 +146,54 @@ const ActionItem = (props: ActionProps) => {
             }}
           />
         </Form.Item>
-        {type1 === 'message' && MessageNodes}
-        {type1 === 'device' && DeviceNodes}
+        {type1 === 'notify' && MessageNodes}
+        {type1 === 'device' && (
+          <DeviceSelect
+            name={props.name}
+            form={props.form}
+            triggerType={props.triggerType}
+            onProperties={setProperties}
+            onMessageTypeChange={setDeviceMessageType}
+            onFunctionChange={setFunctionList}
+            restField={props.restField}
+          />
+        )}
         {type1 === 'delay' && (
-          <InputNumber addonAfter={TimeTypeAfter} style={{ width: 150 }} min={0} max={9999} />
+          <Form.Item name={[name, 'delay']}>
+            <InputNumber />
+          </Form.Item>
         )}
       </div>
-      {type1 === 'message' && templateData ? (
-        <MessageContent form={props.form} template={templateData} name={props.name} />
+      {type1 === 'notify' && templateData ? (
+        <MessageContent
+          form={props.form}
+          template={templateData}
+          name={props.name}
+          notifyType={notifyType}
+          triggerType={props.triggerType}
+          configId={configId}
+        />
+      ) : null}
+      {type1 === 'device' &&
+      deviceMessageType === MessageTypeEnum.WRITE_PROPERTY &&
+      properties.length ? (
+        <Form.Item name={[name, 'device', 'message', 'properties']}>
+          <WriteProperty properties={properties} type={props.triggerType} form={props.form} />
+        </Form.Item>
+      ) : null}
+      {type1 === 'device' &&
+      deviceMessageType === MessageTypeEnum.READ_PROPERTY &&
+      properties.length ? (
+        <Form.Item name={[name, 'device', 'message', 'properties']}>
+          <ReadProperty properties={properties} />
+        </Form.Item>
+      ) : null}
+      {type1 === 'device' &&
+      deviceMessageType === MessageTypeEnum.INVOKE_FUNCTION &&
+      functionList.length ? (
+        <Form.Item name={[name, 'device', 'message', 'inputs']}>
+          <FunctionCall functionData={functionList} />
+        </Form.Item>
       ) : null}
     </div>
   );

+ 152 - 0
src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx

@@ -0,0 +1,152 @@
+import { DatePicker, FormInstance, Input, InputNumber, Select, Space } from 'antd';
+import { useCallback, useEffect, useState } from 'react';
+import { useRequest } from '@@/plugin-request/request';
+import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
+import moment from 'moment';
+
+interface WritePropertyProps {
+  properties: any[];
+  type: string;
+  form: FormInstance;
+  value?: any;
+  onChange?: (value?: any) => void;
+}
+
+export default (props: WritePropertyProps) => {
+  console.log(props.properties);
+  const [source, setSource] = useState('fixed');
+  const [builtInList, setBuiltInList] = useState([]);
+  const [propertiesKey, setPropertiesKey] = useState<string | undefined>(undefined);
+  const [propertiesValue, setPropertiesValue] = useState(undefined);
+  const [propertiesType, setPropertiesType] = useState('');
+
+  const { run: getBuiltInList } = useRequest(queryBuiltInParams, {
+    manual: true,
+    formatResult: (res) => res.result,
+    onSuccess: (res) => {
+      setBuiltInList(res);
+    },
+  });
+
+  useEffect(() => {
+    if (source === 'upper') {
+      getBuiltInList({
+        trigger: { type: props.type },
+      });
+    }
+  }, [source, props.type]);
+
+  useEffect(() => {
+    if (props.value) {
+      if (0 in props.value) {
+        setPropertiesValue(props.value[0]);
+      } else {
+        Object.keys(props.value).forEach((key: string) => {
+          setPropertiesKey(key);
+          setPropertiesValue(props.value[key]);
+        });
+      }
+    }
+  }, [props.value]);
+
+  const onChange = (key?: string, value?: any) => {
+    if (props.onChange) {
+      props.onChange({
+        [key || 0]: value,
+      });
+    }
+  };
+
+  const inputNodeByType = useCallback(
+    (type: string) => {
+      switch (type) {
+        case 'boolean':
+          return (
+            <Select
+              style={{ width: 300, textAlign: 'left' }}
+              value={propertiesValue}
+              options={[
+                { label: 'true', value: true },
+                { label: 'false', value: false },
+              ]}
+              placeholder={'请选择'}
+              onChange={(value) => {
+                onChange(propertiesKey, value);
+              }}
+            />
+          );
+        case 'int':
+        case 'long':
+        case 'float':
+        case 'double':
+          return (
+            <InputNumber
+              style={{ width: 300 }}
+              value={propertiesValue}
+              placeholder={'请输入'}
+              onChange={(value) => {
+                onChange(propertiesKey, value);
+              }}
+            />
+          );
+        case 'date':
+          return (
+            // @ts-ignore
+            <DatePicker
+              style={{ width: 300 }}
+              value={propertiesValue ? moment(propertiesValue) : undefined}
+              onChange={(date) => {
+                onChange(propertiesKey, date ? date.format('YYYY-MM-DD HH:mm:ss') : undefined);
+              }}
+            />
+          );
+        default:
+          return (
+            <Input
+              style={{ width: 300 }}
+              value={propertiesValue}
+              placeholder={'请输入'}
+              onChange={(e) => onChange(propertiesKey, e.target.value)}
+            />
+          );
+      }
+    },
+    [propertiesKey, propertiesValue],
+  );
+
+  return (
+    <Space>
+      <Select
+        value={propertiesKey}
+        options={props.properties}
+        fieldNames={{ label: 'name', value: 'id' }}
+        style={{ width: 120 }}
+        onSelect={(key: any, node: any) => {
+          onChange(key, undefined);
+          setPropertiesType(node.valueType.type);
+        }}
+        placeholder={'请选择属性'}
+      ></Select>
+      <Select
+        value={source}
+        options={[
+          { label: '手动输入', value: 'fixed' },
+          { label: '内置参数', value: 'upper' },
+        ]}
+        style={{ width: 120 }}
+        onChange={(key) => {
+          setSource(key);
+        }}
+      />
+      {source === 'upper' ? (
+        <Select
+          options={builtInList}
+          fieldNames={{ label: 'name', value: 'id' }}
+          placeholder={'请选择参数'}
+        />
+      ) : (
+        <div>{inputNodeByType(propertiesType)}</div>
+      )}
+    </Space>
+  );
+};

+ 180 - 0
src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx

@@ -0,0 +1,180 @@
+import { Badge, Input, message, Modal } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { DeviceItem } from '@/pages/system/Department/typings';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import SearchComponent from '@/components/SearchComponent';
+import ProTable from '@jetlinks/pro-table';
+import { queryDevice } from '@/pages/rule-engine/Scene/Save/action/device/service';
+
+interface DeviceModelProps {
+  value?: ChangeValueType[];
+  onChange?: (value: ChangeValueType[]) => void;
+  productId?: string;
+}
+
+type DeviceBadgeProps = {
+  type: string;
+  text: string;
+};
+
+type ChangeValueType = {
+  name: string;
+  value: string;
+};
+
+const DeviceBadge = (props: DeviceBadgeProps) => {
+  const STATUS = {
+    notActive: 'processing',
+    offline: 'error',
+    online: 'success',
+  };
+  return <Badge status={STATUS[props.type]} text={props.text} />;
+};
+
+export default (props: DeviceModelProps) => {
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const [visible, setVisible] = useState(false);
+  const [selectKeys, setSelectKeys] = useState<ChangeValueType[]>(props.value || []);
+  const [searchParam, setSearchParam] = useState({});
+  const [value, setValue] = useState<ChangeValueType[]>(props.value || []);
+
+  useEffect(() => {
+    setValue(props.value || []);
+    setSelectKeys(props.value || []);
+  }, [props.value]);
+
+  const columns: ProColumns<DeviceItem>[] = [
+    {
+      dataIndex: 'id',
+      title: 'ID',
+      width: 220,
+    },
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.table.name',
+        defaultMessage: '名称',
+      }),
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.device.instance.registrationTime',
+        defaultMessage: '注册时间',
+      }),
+      dataIndex: 'registryTime',
+      valueType: 'dateTime',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.searchTable.titleStatus',
+        defaultMessage: '状态',
+      }),
+      dataIndex: 'state',
+      valueType: 'select',
+      valueEnum: {
+        all: {
+          text: intl.formatMessage({
+            id: 'pages.searchTable.titleStatus.all',
+            defaultMessage: '全部',
+          }),
+          status: 'Default',
+        },
+        onLine: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.onLine',
+            defaultMessage: '在线',
+          }),
+          status: 'onLine',
+        },
+        offLine: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.offLine',
+            defaultMessage: '离线',
+          }),
+          status: 'offLine',
+        },
+        notActive: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.notActive',
+            defaultMessage: '未启用',
+          }),
+          status: 'notActive',
+        },
+      },
+      search: false,
+      render: (_, row) => <DeviceBadge type={row.state.value} text={row.state.text} />,
+    },
+  ];
+
+  return (
+    <>
+      {visible && (
+        <Modal
+          visible={visible}
+          title={'设备'}
+          width={880}
+          onOk={() => {
+            if (props.onChange) {
+              props.onChange(selectKeys);
+            }
+            setVisible(false);
+            actionRef.current?.reload();
+          }}
+          onCancel={() => {
+            setVisible(false);
+            actionRef.current?.reload();
+            setSelectKeys([]);
+          }}
+        >
+          <SearchComponent<DeviceItem>
+            field={columns}
+            enableSave={false}
+            // pattern={'simple'}
+            onSearch={async (data) => {
+              actionRef.current?.reset?.();
+              setSearchParam(data);
+            }}
+            defaultParam={[{ column: 'productId', value: props.productId! }]}
+            target="scene-actions-device"
+          />
+          <ProTable<DeviceItem>
+            actionRef={actionRef}
+            columns={columns}
+            rowKey="id"
+            search={false}
+            rowSelection={{
+              selectedRowKeys: selectKeys.map((item) => item.value),
+              onSelect: (selectedRow: any, selected: any) => {
+                let newSelectKeys = [...selectKeys];
+                if (selected) {
+                  newSelectKeys.push({ name: selectedRow.name, value: selectedRow.id });
+                } else {
+                  newSelectKeys = newSelectKeys.filter((item) => item.value !== selectedRow.id);
+                }
+                setSelectKeys(newSelectKeys);
+              },
+            }}
+            request={(params) => queryDevice(params)}
+            params={searchParam}
+          ></ProTable>
+        </Modal>
+      )}
+      <Input
+        placeholder={'请选择设备'}
+        onClick={() => {
+          if (!props.productId) {
+            message.warning('请选择产品');
+          } else {
+            setVisible(true);
+            setSelectKeys([...value]);
+          }
+        }}
+        style={{ width: 300 }}
+        value={value.map((item) => item.name).toString()}
+        readOnly
+      />
+    </>
+  );
+};

+ 151 - 0
src/pages/rule-engine/Scene/Save/action/device/functionCall.tsx

@@ -0,0 +1,151 @@
+import type { ProColumns } from '@jetlinks/pro-table';
+import { EditableProTable } from '@jetlinks/pro-table';
+import { DatePicker, Input, InputNumber, Select } from 'antd';
+import React, { useEffect, useRef, useState } from 'react';
+import ProForm from '@ant-design/pro-form';
+import type { ProFormInstance } from '@ant-design/pro-form';
+import moment from 'moment';
+
+type FunctionTableDataType = {
+  id: string;
+  name: string;
+  type: string;
+};
+
+interface FunctionCallProps {
+  functionData: any[];
+  value?: any;
+  onChange?: (data: any) => void;
+}
+
+export default (props: FunctionCallProps) => {
+  const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
+  const formRef = useRef<ProFormInstance<any>>();
+
+  useEffect(() => {
+    setEditableRowKeys(props.functionData.map((d) => d.id));
+    formRef.current?.setFieldsValue({
+      table: props.functionData,
+    });
+  }, [props.functionData]);
+
+  useEffect(() => {
+    if (props.functionData && props.functionData.length && props.value) {
+      formRef.current?.setFieldsValue({
+        table: props.functionData.map((item: any) => {
+          const oldValue = props.value.find((oldItem: any) => oldItem.name === item.id);
+          if (oldValue) {
+            return {
+              ...item,
+              value: oldValue.value,
+            };
+          }
+          return item;
+        }),
+      });
+    }
+    console.log(props.value);
+  }, []);
+
+  const getItemNode = (record: any) => {
+    const type = record.type;
+    const name = record.name;
+
+    switch (type) {
+      case 'enum':
+        return (
+          <Select
+            style={{ width: '100%', textAlign: 'left' }}
+            options={record.options}
+            fieldNames={{ label: 'text', value: 'value' }}
+            placeholder={'请选择' + name}
+          />
+        );
+      case 'boolean':
+        return (
+          <Select
+            style={{ width: '100%', textAlign: 'left' }}
+            options={[
+              { label: 'true', value: true },
+              { label: 'false', value: false },
+            ]}
+            placeholder={'请选择' + name}
+          />
+        );
+      case 'int':
+      case 'long':
+      case 'float':
+      case 'double':
+        return <InputNumber style={{ width: '100%' }} placeholder={'请输入' + name} />;
+      case 'date':
+        return (
+          <>
+            {
+              // @ts-ignore
+              <DatePicker
+                format={record.format || 'YYYY-MM-DD HH:mm:ss'}
+                style={{ width: '100%' }}
+              />
+            }
+          </>
+        );
+      default:
+        return <Input placeholder={'请输入' + name} />;
+    }
+  };
+
+  const columns: ProColumns<FunctionTableDataType>[] = [
+    {
+      dataIndex: 'name',
+      title: '参数名称',
+      width: 200,
+      editable: false,
+    },
+    {
+      dataIndex: 'type',
+      title: '类型',
+      width: 200,
+      editable: false,
+    },
+    {
+      title: '值',
+      dataIndex: 'value',
+      align: 'center',
+      width: 260,
+      renderFormItem: (_, row: any) => {
+        return getItemNode(row.record);
+      },
+    },
+  ];
+
+  return (
+    <ProForm<{ table: FunctionTableDataType[] }>
+      formRef={formRef}
+      submitter={false}
+      onValuesChange={() => {
+        const values = formRef.current?.getFieldsValue();
+        if (props.onChange) {
+          props.onChange(
+            values.table.map((item: any) => ({
+              name: item.id,
+              value: item.type === 'date' ? moment(item.value).format(item.format) : item.value,
+            })),
+          );
+        }
+      }}
+    >
+      <EditableProTable
+        rowKey="id"
+        name="table"
+        columns={columns}
+        recordCreatorProps={false}
+        size={'small'}
+        editable={{
+          type: 'multiple',
+          editableKeys,
+          onChange: setEditableRowKeys,
+        }}
+      />
+    </ProForm>
+  );
+};

+ 182 - 0
src/pages/rule-engine/Scene/Save/action/device/index.tsx

@@ -0,0 +1,182 @@
+import { Form, Input, Select } from 'antd';
+import type { FormInstance } from 'antd';
+import { useEffect, useState } from 'react';
+import { getProductList } from '@/pages/rule-engine/Scene/Save/action/device/service';
+import Device from './deviceModal';
+import TagModal from './tagModal';
+
+interface DeviceProps {
+  name: number;
+  triggerType: string;
+  form?: FormInstance;
+  restField?: any;
+  onProperties: (data: any) => void;
+  onMessageTypeChange: (type: string) => void;
+  onFunctionChange: (functionItem: any) => void;
+}
+
+enum SourceEnum {
+  'fixed' = 'fixed',
+  'tag' = 'tag',
+  'relation' = '',
+}
+
+const DefaultSourceOptions = [
+  { label: '固定设备', value: SourceEnum.fixed },
+  { label: '按标签', value: SourceEnum.tag },
+];
+
+export enum MessageTypeEnum {
+  'WRITE_PROPERTY' = 'WRITE_PROPERTY',
+  'READ_PROPERTY' = 'READ_PROPERTY',
+  'INVOKE_FUNCTION' = 'INVOKE_FUNCTION',
+}
+
+export default (props: DeviceProps) => {
+  const { name } = props;
+
+  const [productId, setProductId] = useState<string>('');
+  const [sourceList, setSourceList] = useState(DefaultSourceOptions);
+  const [productList, setProductList] = useState<any[]>([]);
+  const [selector, setSelector] = useState(SourceEnum.fixed);
+  const [messageType, setMessageType] = useState(MessageTypeEnum.WRITE_PROPERTY);
+  const [functionList, setFunctionList] = useState([]);
+  const [tagList, setTagList] = useState([]);
+
+  const getProducts = async () => {
+    const resp = await getProductList({ paging: false });
+    if (resp && resp.status === 200) {
+      setProductList(resp.result);
+    }
+  };
+  useEffect(() => {
+    props.form?.resetFields([['actions', name, 'device', 'selector']]);
+    if (props.triggerType === 'device') {
+      setSourceList([...DefaultSourceOptions, { label: '按关系', value: SourceEnum.relation }]);
+    } else {
+      setSourceList(DefaultSourceOptions);
+    }
+  }, [props.triggerType]);
+
+  useEffect(() => {
+    getProducts();
+  }, []);
+
+  const handleMetadata = (metadata?: string) => {
+    try {
+      const metadataObj = JSON.parse(metadata || '{}');
+      if (props.onProperties) {
+        props.onProperties(metadataObj.properties || []);
+      }
+      if (!metadataObj.functions) {
+        if (props.onFunctionChange) {
+          props.onFunctionChange([]);
+        }
+      }
+      setTagList(metadataObj.tags || []);
+      setFunctionList(metadataObj.functions || []);
+    } catch (err) {
+      console.warn('handleMetadata === ', err);
+    }
+  };
+
+  return (
+    <>
+      <Form.Item name={[name, 'device', 'productId']}>
+        <Select
+          options={productList}
+          placeholder={'请选择产品'}
+          style={{ width: 220 }}
+          listHeight={220}
+          onChange={(key: any, node: any) => {
+            props.form?.resetFields([['actions', name, 'device', 'selector']]);
+            props.form?.resetFields([['actions', name, 'device', 'selectorValues']]);
+            props.form?.resetFields([['actions', name, 'device', 'message', 'functionId']]);
+            // setMessageType(MessageTypeEnum.WRITE_PROPERTY)
+            setProductId(key);
+            handleMetadata(node.metadata);
+          }}
+          fieldNames={{ label: 'name', value: 'id' }}
+        />
+      </Form.Item>
+      <Form.Item
+        name={[name, 'device', 'selector']}
+        initialValue={SourceEnum.fixed}
+        {...props.restField}
+      >
+        <Select
+          options={sourceList}
+          style={{ width: 120 }}
+          onChange={(key) => {
+            setSelector(key);
+          }}
+        />
+      </Form.Item>
+      {selector === SourceEnum.fixed && (
+        <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+          <Device productId={productId} />
+        </Form.Item>
+      )}
+      {selector === SourceEnum.tag && (
+        <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+          <TagModal tagData={tagList} />
+        </Form.Item>
+      )}
+      {selector === SourceEnum.relation && (
+        <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+          <Select style={{ width: 300 }} />
+        </Form.Item>
+      )}
+      <Form.Item
+        name={[name, 'device', 'message', 'messageType']}
+        initialValue={MessageTypeEnum.WRITE_PROPERTY}
+        {...props.restField}
+      >
+        <Select
+          options={[
+            { label: '功能调用', value: MessageTypeEnum.INVOKE_FUNCTION },
+            { label: '读取属性', value: MessageTypeEnum.READ_PROPERTY },
+            { label: '设置属性', value: MessageTypeEnum.WRITE_PROPERTY },
+          ]}
+          onSelect={(key: any) => {
+            if (props.onMessageTypeChange) {
+              props.onMessageTypeChange(key);
+            }
+            setMessageType(key);
+          }}
+          style={{ width: 120 }}
+        />
+      </Form.Item>
+      {messageType === MessageTypeEnum.INVOKE_FUNCTION ? (
+        <Form.Item name={[name, 'device', 'message', 'functionId']}>
+          <Select
+            options={functionList}
+            fieldNames={{ label: 'name', value: 'id' }}
+            style={{ width: 120 }}
+            placeholder={'请选择功能'}
+            onSelect={(_: any, data: any) => {
+              const properties = data.valueType ? data.valueType.properties : data.inputs;
+              if (props.onFunctionChange) {
+                const array = [];
+                for (const datum of properties) {
+                  array.push({
+                    id: datum.id,
+                    name: datum.name,
+                    type: datum.valueType ? datum.valueType.type : '-',
+                    format: datum.valueType ? datum.valueType.format : undefined,
+                    options: datum.valueType ? datum.valueType.elements : undefined,
+                    value: undefined,
+                  });
+                }
+                props.onFunctionChange(array);
+              }
+            }}
+          />
+        </Form.Item>
+      ) : null}
+      <Form.Item name={[name, 'device', 'source']} hidden>
+        <Input />
+      </Form.Item>
+    </>
+  );
+};

+ 24 - 0
src/pages/rule-engine/Scene/Save/action/device/readProperty.tsx

@@ -0,0 +1,24 @@
+import { Select } from 'antd';
+
+interface ReadPropertyProps {
+  properties: any[];
+  value?: any;
+  onChange?: (value?: any) => void;
+}
+
+export default (props: ReadPropertyProps) => {
+  return (
+    <Select
+      value={props.value ? props.value[0] : undefined}
+      options={props.properties}
+      fieldNames={{ label: 'name', value: 'id' }}
+      style={{ width: 120 }}
+      onSelect={(key: any) => {
+        if (props.onChange) {
+          props.onChange([key]);
+        }
+      }}
+      placeholder={'请选择属性'}
+    ></Select>
+  );
+};

+ 13 - 0
src/pages/rule-engine/Scene/Save/action/device/service.ts

@@ -0,0 +1,13 @@
+import { request } from '@@/plugin-request/request';
+import SystemConst from '@/utils/const';
+
+// 获取设备
+export const queryDevice = (data: any) =>
+  request(`${SystemConst.API_BASE}/device-instance/_query`, {
+    method: 'POST',
+    data: data,
+  });
+
+// 获取产品
+export const getProductList = (params?: any) =>
+  request(`/${SystemConst.API_BASE}/device/product/_query/no-paging`, { method: 'GET', params });

+ 0 - 0
src/pages/rule-engine/Scene/Save/action/device/sourceItem.tsx


+ 263 - 0
src/pages/rule-engine/Scene/Save/action/device/tagModal.tsx

@@ -0,0 +1,263 @@
+import { Modal, Input, Space, Select, InputNumber, DatePicker, Row, Col, Button } from 'antd';
+import { useEffect, useState } from 'react';
+import moment from 'moment';
+import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
+
+type TagValueType = {
+  column: string;
+  value: any;
+  type: string;
+};
+
+interface TagModalProps {
+  tagData: any[];
+  value?: TagValueType[];
+  onChange?: (value: any[]) => void;
+}
+
+/**
+ * 数据格式 [{"value":{"key":"value"},"name":"标签名称"}]
+ * @param props
+ */
+export default (props: TagModalProps) => {
+  const [visible, setVisible] = useState(false);
+  const [tagList, setTagList] = useState<any[]>([{}]);
+  const [options, setOptions] = useState<any[]>([]);
+  const [nameList, setNameList] = useState<string[]>([]);
+
+  const handleItem = (data: any) => {
+    return {
+      ...data,
+      valueType: data.valueType ? data.valueType.type : '-',
+      format: data.valueType ? data.valueType.format : undefined,
+      options: data.valueType ? data.valueType.elements : undefined,
+      value: data.value,
+    };
+  };
+
+  useEffect(() => {
+    if (visible) {
+      setOptions(
+        props.tagData.map((item: any) => {
+          return { label: item.name, value: item.id, ...item };
+        }),
+      );
+    }
+  }, [visible, props.tagData]);
+
+  useEffect(() => {
+    if (props.value) {
+      const names: string[] = [];
+      const newTagList = props.value
+        .filter((valueItem) => {
+          return props.tagData.some((item) => valueItem.column === item.id);
+        })
+        .map((valueItem) => {
+          const oldItem = props.tagData.find((item) => item.id === valueItem.column);
+          if (oldItem) {
+            names.push(oldItem.name);
+            return {
+              ...handleItem(oldItem),
+              type: valueItem.type,
+            };
+          }
+          return valueItem;
+        });
+      setNameList(names);
+      setTagList(newTagList);
+    } else {
+      setTagList([{}]);
+    }
+  }, [props.value]);
+
+  const getItemNode = (record: any) => {
+    const type = record.valueType;
+    const name = record.name;
+
+    switch (type) {
+      case 'enum':
+        return (
+          <Select
+            value={record.value}
+            style={{ width: '100%', textAlign: 'left' }}
+            options={record.options}
+            fieldNames={{ label: 'text', value: 'value' }}
+            placeholder={'请选择' + name}
+            onChange={(key) => {
+              record.value = key;
+            }}
+          />
+        );
+      case 'boolean':
+        return (
+          <Select
+            value={record.value}
+            style={{ width: '100%', textAlign: 'left' }}
+            options={[
+              { label: 'true', value: true },
+              { label: 'false', value: false },
+            ]}
+            placeholder={'请选择' + name}
+            onChange={(key) => {
+              record.value = key;
+            }}
+          />
+        );
+      case 'int':
+      case 'long':
+      case 'float':
+      case 'double':
+        return (
+          <InputNumber
+            value={record.value}
+            style={{ width: '100%' }}
+            placeholder={'请输入' + name}
+            onChange={(key) => {
+              record.value = key;
+            }}
+          />
+        );
+      case 'date':
+        return (
+          <>
+            {
+              // @ts-ignore
+              <DatePicker
+                value={record.value && moment(record.value)}
+                format={record.format || 'YYYY-MM-DD HH:mm:ss'}
+                style={{ width: '100%' }}
+                onChange={(_, date) => {
+                  record.value = date;
+                }}
+              />
+            }
+          </>
+        );
+      default:
+        return (
+          <Input
+            value={record.value}
+            placeholder={'请输入标签值'}
+            onChange={(e) => {
+              record.value = e.target.value;
+            }}
+          />
+        );
+    }
+  };
+
+  return (
+    <>
+      <Modal
+        visible={visible}
+        title={'设备'}
+        width={660}
+        onOk={() => {
+          const newValue = tagList
+            .filter((item) => !!item.value)
+            .map((item: any) => {
+              return {
+                column: item.id,
+                type: item.type,
+                value: item.value,
+              };
+            });
+          if (props.onChange) {
+            props.onChange(newValue);
+          }
+          setVisible(false);
+          setTagList([{}]);
+        }}
+        onCancel={() => {
+          setVisible(false);
+          setTagList([{}]);
+          setOptions([]);
+        }}
+      >
+        <div>
+          {tagList.map((tag, index) => (
+            <Row gutter={12} key={tag.id || index} style={{ marginBottom: 12 }}>
+              <Col span={4}>
+                {index === 0 ? (
+                  <span>标签选择</span>
+                ) : (
+                  <Select
+                    value={tag.type}
+                    options={[
+                      { label: '并且', value: 'and' },
+                      { label: '或者', value: 'or' },
+                    ]}
+                    style={{ width: '100%' }}
+                    onSelect={(key: string) => {
+                      const indexItem = tagList[index];
+                      indexItem.type = key;
+                      tagList[index] = indexItem;
+                      setTagList([...tagList]);
+                    }}
+                  />
+                )}
+              </Col>
+              <Col span={16}>
+                <Row gutter={12}>
+                  <Col flex="120px">
+                    <Select
+                      value={tag.id}
+                      style={{ width: '120px' }}
+                      options={options}
+                      onSelect={(_: any, data: any) => {
+                        const newList = [...tagList];
+                        const indexType = newList[index].type;
+                        newList.splice(
+                          index,
+                          1,
+                          handleItem({ ...data, value: undefined, type: indexType }),
+                        );
+                        setTagList(newList);
+                      }}
+                      placeholder={'请选择标签'}
+                    />
+                  </Col>
+                  <Col flex={'auto'}>{getItemNode(tag)}</Col>
+                </Row>
+              </Col>
+              <Col span={4}>
+                <Space>
+                  <Button
+                    style={{ padding: '0 8px' }}
+                    onClick={() => {
+                      setTagList([...tagList, { type: 'and' }]);
+                    }}
+                  >
+                    <PlusOutlined />
+                  </Button>
+                  {tagList.length !== 1 && (
+                    <Button
+                      style={{ padding: '0 8px' }}
+                      onClick={() => {
+                        const newTagList = [...tagList];
+                        newTagList.splice(index, 1);
+                        setTagList(newTagList);
+                      }}
+                      danger
+                    >
+                      <DeleteOutlined />
+                    </Button>
+                  )}
+                </Space>
+              </Col>
+            </Row>
+          ))}
+        </div>
+      </Modal>
+      <Input
+        value={nameList.length ? nameList.toString() : undefined}
+        readOnly
+        style={{ width: 300 }}
+        onClick={() => {
+          setVisible(true);
+        }}
+        placeholder={'请选择标签'}
+      />
+    </>
+  );
+};

+ 41 - 133
src/pages/rule-engine/Scene/Save/action/messageContent.tsx

@@ -1,66 +1,25 @@
-import { Col, Form, Row, Select, Input, DatePicker, InputNumber } from 'antd';
+import { Col, Form, Row } from 'antd';
 import type { FormInstance } from 'antd';
-import { ItemGroup, InputFile } from '@/pages/rule-engine/Scene/Save/components';
-import { useEffect, useState } from 'react';
-import { TriggerWayType } from '@/pages/rule-engine/Scene/Save/components/TriggerWay';
+import {
+  BuiltIn,
+  OrgList,
+  UserList,
+  TagSelect,
+} from '@/pages/rule-engine/Scene/Save/action/VariableItems';
+import { InputFile } from '@/pages/rule-engine/Scene/Save/components';
 
 interface MessageContentProps {
   name: number;
   template?: any;
   form: FormInstance;
+  notifyType: string;
+  triggerType: string;
+  configId: string;
 }
 
 const rowGutter = 12;
 
-const BuiltInSelectOptions = {
-  [TriggerWayType.timing]: [
-    { label: '设备名称', value: 'device-name' },
-    { label: '设备ID', value: 'device-id' },
-    { label: '产品名称', value: 'product-name' },
-    { label: '产品ID', value: 'product-id' },
-    { label: '系统时间', value: 'device-name' },
-    { label: '设备名称', value: 'device-name' },
-  ],
-  [TriggerWayType.manual]: [],
-  [TriggerWayType.device]: [],
-};
-
 export default (props: MessageContentProps) => {
-  const [triggerType, setTriggerType] = useState('');
-
-  useEffect(() => {
-    const trigger = props.form.getFieldsValue([['trigger', 'type']]);
-    if (trigger) {
-      setTriggerType(trigger.type);
-    }
-  }, []);
-
-  // const inputNodeByType = useCallback((data: any) => {
-  //   const { actions } = props.form.getFieldsValue([['actions',props.name, 'notify']])
-  //   console.log(actions);
-  //   if (actions && actions[props.name].notify && actions[props.name].notify.variables) {
-  //     const type = actions[props.name].notify.variables[data.id].type
-  //
-  //     if (type === 2) {
-  //       return <Select options={BuiltInSelectOptions[triggerType] || []} style={{ width: '100%'}} />
-  //     }
-  //   }
-  //
-  //   switch (data.type) {
-  //     case 'enum':
-  //       return <Select placeholder={`请选择${data.name}`} style={{ width: '100%' }} />;
-  //     case 'date':
-  //       // @ts-ignore
-  //       return <DatePicker style={{ width: '100%' }} format={data.format || 'YYYY-MM-DD HH:mm:ss'} />;
-  //     case 'number':
-  //       return <InputNumber placeholder={`请输入${data.name}`} style={{ width: '100%' }} />;
-  //     case 'file':
-  //       return <InputFile />;
-  //     default:
-  //       return <Input placeholder={`请输入${data.name}`} />;
-  //   }
-  // }, [triggerType]);
-
   return (
     <>
       {props.template && (
@@ -68,89 +27,38 @@ export default (props: MessageContentProps) => {
           {props.template.variableDefinitions ? (
             <Row gutter={rowGutter}>
               {props.template.variableDefinitions.map((item: any, index: number) => {
+                const type = item.expands?.businessType || item.type;
+                const _name = [props.name, 'notify', 'variables', item.id];
+                let initialValue = undefined;
+                if (type === 'user') {
+                  initialValue = {
+                    source: 'relation',
+                    value: undefined,
+                  };
+                } else if (['date', 'number', 'string'].includes(type)) {
+                  initialValue = {
+                    source: 'fixed',
+                    value: undefined,
+                  };
+                }
                 return (
                   <Col span={12} key={`${item.id}_${index}`}>
-                    <Form.Item label={item.name} style={{ margin: 0 }}>
-                      <ItemGroup>
-                        <Form.Item
-                          name={[props.name, 'notify', 'variables', item.id, 'type']}
-                          initialValue={'1'}
-                        >
-                          <Select
-                            options={[
-                              { label: '手动输入', value: '1' },
-                              { label: '内置参数', value: '2' },
-                            ]}
-                            style={{ width: 120 }}
-                          />
-                        </Form.Item>
-                        <Form.Item
-                          name={[props.name, 'notify', 'variables', item.id, 'value']}
-                          shouldUpdate={(prevValues, curValues) => {
-                            const oldNotify = prevValues.actions[props.name].notify.variables;
-                            const curNotify = curValues.actions[props.name].notify.variables;
-
-                            if (oldNotify && curNotify) {
-                              if (oldNotify[item.id] && curNotify[item.id]) {
-                                return oldNotify[item.id].type !== curNotify[item.id].type;
-                              }
-                            }
-                            return false;
-                          }}
-                        >
-                          {() => {
-                            const { actions } = props.form.getFieldsValue([
-                              ['actions', props.name, 'notify'],
-                            ]);
-                            console.log(actions);
-                            if (
-                              actions &&
-                              actions[props.name].notify &&
-                              actions[props.name].notify.variables
-                            ) {
-                              const type = actions[props.name].notify.variables[item.id].type;
-
-                              if (type === 2) {
-                                return (
-                                  <Select
-                                    options={BuiltInSelectOptions[triggerType] || []}
-                                    style={{ width: '100%' }}
-                                  />
-                                );
-                              }
-                            }
-
-                            switch (item.type) {
-                              case 'enum':
-                                return (
-                                  <Select
-                                    placeholder={`请选择${item.name}`}
-                                    style={{ width: '100%' }}
-                                  />
-                                );
-                              case 'date':
-                                return (
-                                  // @ts-ignore
-                                  <DatePicker
-                                    style={{ width: '100%' }}
-                                    format={item.format || 'YYYY-MM-DD HH:mm:ss'}
-                                  />
-                                );
-                              case 'number':
-                                return (
-                                  <InputNumber
-                                    placeholder={`请输入${item.name}`}
-                                    style={{ width: '100%' }}
-                                  />
-                                );
-                              case 'file':
-                                return <InputFile />;
-                              default:
-                                return <Input placeholder={`请输入${item.name}`} />;
-                            }
-                          }}
-                        </Form.Item>
-                      </ItemGroup>
+                    <Form.Item name={_name} label={item.name} initialValue={initialValue}>
+                      {type === 'user' ? (
+                        <UserList
+                          notifyType={props.notifyType}
+                          configId={props.configId}
+                          type={props.triggerType}
+                        />
+                      ) : type === 'org' ? (
+                        <OrgList notifyType={props.notifyType} configId={props.configId} />
+                      ) : type === 'file' ? (
+                        <InputFile />
+                      ) : type === 'tag' ? (
+                        <TagSelect configId={props.configId} />
+                      ) : (
+                        <BuiltIn type={props.triggerType} data={item} />
+                      )}
                     </Form.Item>
                   </Col>
                 );

+ 40 - 0
src/pages/rule-engine/Scene/Save/action/service.ts

@@ -29,3 +29,43 @@ export const queryProductList = (data?: any) =>
 
 export const queryDeviceSelector = () =>
   request(`${SystemConst.API_BASE}/scene/device-selectors`, { method: 'GET' });
+
+// 内置参数
+export const queryBuiltInParams = (data: any) =>
+  request(`${SystemConst.API_BASE}/scene/parse-variables`, { method: 'POST', data });
+
+// 平台用户
+export const queryPlatformUsers = () =>
+  request(`${SystemConst.API_BASE}/user/_query/no-paging`, {
+    method: 'POST',
+    data: { paging: false, sorts: [{ name: 'name', order: 'asc' }] },
+  });
+
+// 关系用户
+export const queryRelationUsers = () =>
+  request(`${SystemConst.API_BASE}/relation/_query/no-paging`, {
+    method: 'POST',
+    data: { paging: false, sorts: [{ name: 'name', order: 'asc' }] },
+  });
+
+// 钉钉用户
+export const queryDingTalkUsers = (id: string) =>
+  request(`${SystemConst.API_BASE}/notifier/dingtalk/corp/${id}/users`, { method: 'GET' });
+
+// 钉钉部门
+export const queryDingTalkDepartments = (id: string) =>
+  request(`${SystemConst.API_BASE}/notifier/dingtalk/corp/${id}/departments/tree`, {
+    method: 'GET',
+  });
+
+// 微信用户
+export const queryWechatUsers = (id: string) =>
+  request(`${SystemConst.API_BASE}/notifier/wechat/corp/${id}/users`, { method: 'GET' });
+
+// 微信部门
+export const queryWechatDepartments = (id: string) =>
+  request(`${SystemConst.API_BASE}/notifier/wechat/corp/${id}/departments`, { method: 'GET' });
+
+// 根据配置ID获取标签推送
+export const queryTag = (id: string) =>
+  request(`${SystemConst.API_BASE}/notifier/wechat/corp/${id}/tags`, { method: 'GET' });

+ 28 - 0
src/pages/rule-engine/Scene/Save/components/DatePickerFormat/index.tsx

@@ -0,0 +1,28 @@
+import { DatePicker } from 'antd';
+import type { DatePickerProps } from 'antd';
+import moment from 'moment';
+
+interface DatePickerFormat extends Omit<DatePickerProps, 'onChange'> {
+  onChange?: (dateString: string, date: moment.Moment | null) => void;
+}
+
+export default (props: DatePickerFormat) => {
+  const { value, onChange, ...extraProps } = props;
+
+  return (
+    <>
+      {
+        // @ts-ignore
+        <DatePicker
+          {...extraProps}
+          value={typeof value === 'string' ? moment(value) : value}
+          onChange={(date, dateString) => {
+            if (onChange) {
+              onChange(dateString, date);
+            }
+          }}
+        />
+      }
+    </>
+  );
+};

+ 53 - 0
src/pages/rule-engine/Scene/Save/components/InputNumber.tsx

@@ -0,0 +1,53 @@
+import { InputNumber, Select } from 'antd';
+import { useEffect, useState } from 'react';
+
+interface InputNumberProps {
+  value?: { time: number; unit: string };
+  onChange?: (value: { time: number; unit: string }) => void;
+}
+
+export default (props: InputNumberProps) => {
+  const [time, setTime] = useState(props.value?.time || 0);
+  const [unit, setUnit] = useState(props.value?.unit || 'seconds');
+
+  useEffect(() => {
+    setTime(props.value?.time || 0);
+    setUnit(props.value?.unit || 'seconds');
+  }, [props.value]);
+
+  const TimeTypeAfter = (
+    <Select
+      value={unit}
+      options={[
+        { label: '秒', value: 'seconds' },
+        { label: '分', value: 'minutes' },
+        { label: '小时', value: 'hours' },
+      ]}
+      onChange={(key) => {
+        if (props.onChange) {
+          props.onChange({
+            time: time,
+            unit: key,
+          });
+        }
+      }}
+    />
+  );
+
+  return (
+    <InputNumber
+      addonAfter={TimeTypeAfter}
+      style={{ width: 150 }}
+      min={0}
+      max={9999}
+      onChange={(value) => {
+        if (props.onChange) {
+          props.onChange({
+            time: value,
+            unit,
+          });
+        }
+      }}
+    />
+  );
+};

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

@@ -13,7 +13,7 @@ interface InputUploadProps {
 export default (props: InputUploadProps) => {
   const { onChange } = props;
 
-  const [url, setUrl] = useState('');
+  const [url, setUrl] = useState(props.value);
   const [loading, setLoading] = useState<boolean>(false);
 
   useEffect(() => {

+ 7 - 2
src/pages/rule-engine/Scene/Save/components/TimeSelect/index.tsx

@@ -8,8 +8,8 @@ type OptionsItemType = {
 
 interface TimeSelectProps {
   options?: OptionsItemType[];
-  value?: string;
-  onChange?: (value: string[]) => void;
+  value?: number[];
+  onChange?: (value: number[]) => void;
   style?: React.CSSProperties;
 }
 
@@ -39,6 +39,11 @@ export default (props: TimeSelectProps) => {
       value={checkedKeys}
       onChange={onChange}
       style={props.style}
+      maxTagCount={0}
+      placeholder={'请选择时间'}
+      maxTagPlaceholder={(values) => {
+        return <span className={''}>{values.map((item) => item.label).toString()}</span>;
+      }}
       treeData={
         props.options && props.options.length
           ? [{ label: '全部', value: 'all' }, ...props.options]

+ 179 - 34
src/pages/rule-engine/Scene/Save/components/TimingTrigger/index.tsx

@@ -1,45 +1,177 @@
 import { Input, InputNumber, Select, TimePicker } from 'antd';
 import { TimeSelect } from '@/pages/rule-engine/Scene/Save/components';
-import { useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
+import { omit } from 'lodash';
+import moment from 'moment';
 
-export default () => {
-  const [type1, setType1] = useState(1);
-  const [type2, setType2] = useState(1);
+type TimerType = {
+  trigger: string;
+  cron?: string;
+  when?: number[];
+  mod?: string;
+  period?: {
+    from?: string;
+    to?: string;
+    every?: number;
+    unit?: string;
+  };
+  once?: {
+    time?: string;
+  };
+};
+
+interface TimingTrigger {
+  value?: TimerType;
+  onChange?: (value: TimerType) => void;
+}
+
+enum TriggerEnum {
+  'week' = 'week',
+  'month' = 'month',
+  'cron' = 'cron',
+}
+
+enum PeriodModEnum {
+  'period' = 'period',
+  'once' = 'once',
+}
+
+const DefaultValue = {
+  trigger: TriggerEnum.week,
+  mod: PeriodModEnum.period,
+  period: {},
+};
 
-  const type1Select = async (key: number) => {
-    setType1(key);
-    if (key !== 3) {
-      // TODO 请求后端返回天数
+export default (props: TimingTrigger) => {
+  const [data, setData] = useState<TimerType>(DefaultValue);
+
+  useEffect(() => {
+    setData(props.value || DefaultValue);
+  }, [props.value]);
+
+  const onChange = (newData: TimerType) => {
+    if (props.onChange) {
+      props.onChange(newData);
     }
   };
 
-  const type2Select = (key: number) => {
-    setType2(key);
+  const type1Select = async (key: string) => {
+    if (key !== TriggerEnum.cron) {
+      onChange({
+        trigger: key,
+        mod: PeriodModEnum.period,
+        when: [],
+        period: {
+          unit: 'second',
+        },
+      });
+    } else {
+      onChange({
+        trigger: key,
+        cron: undefined,
+      });
+    }
   };
 
+  const type2Select = useCallback(
+    (key: string) => {
+      if (key === PeriodModEnum.period) {
+        onChange({
+          ...omit(data, 'once'),
+          mod: key,
+          period: {
+            from: undefined,
+            to: undefined,
+            unit: 'second',
+          },
+        });
+      } else {
+        onChange({
+          ...omit(data, 'period'),
+          mod: key,
+          once: {
+            time: undefined,
+          },
+        });
+      }
+    },
+    [data],
+  );
+
   const TimeTypeAfter = (
     <Select
-      defaultValue={'second'}
+      value={data.period?.unit || 'second'}
       options={[
         { label: '秒', value: 'second' },
         { label: '分', value: 'minute' },
         { label: '小时', value: 'hour' },
       ]}
+      onSelect={(key: string) => {
+        onChange({
+          ...data,
+          period: {
+            ...data.period,
+            unit: key,
+          },
+        });
+      }}
     />
   );
 
   const implementNode =
-    type2 === 1 ? (
+    data.mod === PeriodModEnum.period ? (
       <>
-        <TimePicker.RangePicker />
+        <TimePicker.RangePicker
+          format={'hh:mm:ss'}
+          value={
+            data.period?.from
+              ? [moment(data.period?.from, 'hh:mm:ss'), moment(data.period?.to, 'hh:mm:ss')]
+              : undefined
+          }
+          onChange={(_, dateString) => {
+            onChange({
+              ...data,
+              period: {
+                ...data.period,
+                from: dateString[0],
+                to: dateString[1],
+              },
+            });
+          }}
+        />
         <span> 每 </span>
-        <InputNumber addonAfter={TimeTypeAfter} style={{ width: 150 }} min={0} max={9999} />
+        <InputNumber
+          value={data.period?.every}
+          addonAfter={TimeTypeAfter}
+          style={{ width: 150 }}
+          min={0}
+          max={9999}
+          onChange={(e) => {
+            onChange({
+              ...data,
+              period: {
+                ...data.period,
+                every: e,
+              },
+            });
+          }}
+        />
         <span> 执行一次 </span>
       </>
     ) : (
       <>
-        <TimePicker />
-        <InputNumber addonAfter={TimeTypeAfter} style={{ width: 150 }} min={0} max={9999} />
+        <TimePicker
+          format={'hh:mm:ss'}
+          value={data.once?.time ? moment(data.once?.time, 'hh:mm:ss') : undefined}
+          onChange={(_, dateString) => {
+            onChange({
+              ...data,
+              once: {
+                time: dateString,
+              },
+            });
+          }}
+        />
         <span> 执行一次 </span>
       </>
     );
@@ -48,34 +180,47 @@ export default () => {
     <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
       <Select
         options={[
-          { label: '按周', value: 1 },
-          { label: '按月', value: 2 },
-          { label: 'cron表达式', value: 3 },
+          { label: '按周', value: TriggerEnum.week },
+          { label: '按月', value: TriggerEnum.month },
+          { label: 'cron表达式', value: TriggerEnum.cron },
         ]}
-        value={type1}
+        value={data.trigger}
         onSelect={type1Select}
         style={{ width: 120 }}
       />
-      {type1 !== 3 ? (
+      {data.trigger !== TriggerEnum.cron ? (
         <>
           <TimeSelect
-            options={[
-              { label: '周一', value: 1 },
-              { label: '周二', value: 2 },
-              { label: '周三', value: 3 },
-              { label: '周四', value: 4 },
-              { label: '周五', value: 5 },
-              { label: '周六', value: 6 },
-              { label: '周末', value: 7 },
-            ]}
-            style={{ width: 150 }}
+            value={data.when}
+            options={
+              data.trigger === TriggerEnum.week
+                ? [
+                    { label: '周一', value: 1 },
+                    { label: '周二', value: 2 },
+                    { label: '周三', value: 3 },
+                    { label: '周四', value: 4 },
+                    { label: '周五', value: 5 },
+                    { label: '周六', value: 6 },
+                    { label: '周末', value: 7 },
+                  ]
+                : new Array(31)
+                    .fill(1)
+                    .map((_, index) => ({ label: index + 1 + '号', value: index + 1 }))
+            }
+            style={{ width: 180 }}
+            onChange={(keys) => {
+              onChange({
+                ...data,
+                when: keys,
+              });
+            }}
           />
           <Select
             options={[
-              { label: '周期执行', value: 1 },
-              { label: '执行一次', value: 2 },
+              { label: '周期执行', value: PeriodModEnum.period },
+              { label: '执行一次', value: PeriodModEnum.once },
             ]}
-            value={type2}
+            value={data.mod}
             style={{ width: 150 }}
             onSelect={type2Select}
           />

+ 5 - 0
src/pages/rule-engine/Scene/Save/components/TriggerWay/index.tsx

@@ -5,6 +5,7 @@ import './index.less';
 interface TriggerWayProps {
   value?: string;
   onChange?: (type: string) => void;
+  onSelect?: (type: string) => void;
 }
 
 export enum TriggerWayType {
@@ -23,6 +24,10 @@ export default (props: TriggerWayProps) => {
   const onSelect = (_type: string) => {
     setType(_type);
 
+    if (props.onSelect) {
+      props.onSelect(_type);
+    }
+
     if (props.onChange) {
       props.onChange(_type);
     }

+ 2 - 0
src/pages/rule-engine/Scene/Save/components/index.ts

@@ -3,3 +3,5 @@ export { default as TimingTrigger } from './TimingTrigger';
 export { default as TriggerWay } from './TriggerWay';
 export { default as ItemGroup } from './ItemGroup';
 export { default as InputFile } from './InputUpload';
+export { default as DatePickerFormat } from './DatePickerFormat';
+export { default as InputNumber } from './InputNumber';

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

@@ -1,12 +1,34 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { Button, Card, Form, Input } from 'antd';
+import {
+  Button,
+  Card,
+  Form,
+  Input,
+  InputNumber,
+  message,
+  Radio,
+  Space,
+  Switch,
+  Tooltip,
+} from 'antd';
 import { useLocation } from 'umi';
 import { useEffect, useRef, useState } from 'react';
 import { PermissionButton } from '@/components';
 import ActionItems from './action/action';
-import { PlusOutlined } from '@ant-design/icons';
-import { TriggerWay } from './components';
+import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import { TimingTrigger, TriggerWay } from './components';
+import { TriggerWayType } from './components/TriggerWay';
 import TriggerTerm from '@/pages/rule-engine/Scene/TriggerTerm';
+import TriggerDevice from './trigger';
+import { service } from '../index';
+
+type ShakeLimitType = {
+  enabled: boolean;
+  groupType?: string;
+  time?: number;
+  threshold?: number;
+  alarmFirst?: boolean;
+};
 
 export default () => {
   const location = useLocation();
@@ -14,6 +36,13 @@ export default () => {
   const triggerRef = useRef<any>();
 
   const { getOtherPermission } = PermissionButton.usePermission('rule-engine/Scene');
+  const [triggerType, setTriggerType] = useState('');
+  // const [triggerValue, setTriggerValue] = useState<any>();
+  const [parallel, setParallel] = useState(false); // 是否并行
+  const [shakeLimit, setShakeLimit] = useState<ShakeLimitType>({
+    enabled: false,
+    alarmFirst: true,
+  });
 
   const getDetail = async () => {
     // TODO 回显数据
@@ -29,13 +58,27 @@ export default () => {
 
   const saveData = async () => {
     const formData = await form.validateFields();
+    let triggerData = undefined;
     // 获取触发条件数据
-    const triggerData = await triggerRef.current.getTriggerForm();
-    console.log(JSON.stringify(triggerData), 'trigger');
+    if (triggerRef.current) {
+      triggerData = await triggerRef.current.getTriggerForm();
+      console.log(JSON.stringify(triggerData), 'trigger');
+      if (!triggerData) {
+        return;
+      }
+      formData.terms = triggerData.trigger;
+    }
     console.log(formData);
+    if (formData) {
+      const resp = formData.id ? await service.update(formData) : await service.save(formData);
+      if (resp.status === 200) {
+        message.success('操作成功');
+      } else {
+        message.error(resp.message);
+      }
+    }
   };
 
-  const [triggerValue, setTriggerValue] = useState<any>();
   const requestParams = {
     trigger: {
       type: 'device',
@@ -83,6 +126,67 @@ export default () => {
     },
   };
 
+  const AntiShake = (
+    <Space>
+      <span>触发条件</span>
+      <Switch
+        checked={shakeLimit.enabled}
+        checkedChildren="开启防抖"
+        unCheckedChildren="关闭防抖"
+        onChange={(e) => {
+          setShakeLimit({
+            ...shakeLimit,
+            enabled: e,
+          });
+          form.setFieldsValue({ shakeLimit });
+        }}
+      />
+      {shakeLimit.enabled && (
+        <>
+          <InputNumber
+            value={shakeLimit.time}
+            onChange={(e: number) => {
+              setShakeLimit({
+                ...shakeLimit,
+                time: e,
+              });
+              form.setFieldsValue({ shakeLimit });
+            }}
+          />
+          <span> 秒内发生 </span>
+          <InputNumber
+            value={shakeLimit.threshold}
+            onChange={(e: number) => {
+              setShakeLimit({
+                ...shakeLimit,
+                threshold: e,
+              });
+              form.setFieldsValue({ shakeLimit });
+            }}
+          />
+          <span>次及以上时,处理</span>
+          <Radio.Group
+            value={shakeLimit.alarmFirst}
+            options={[
+              { label: '第一次', value: true },
+              { label: '最后一次', value: false },
+            ]}
+            optionType="button"
+            onChange={(e) => {
+              console.log(e);
+              setShakeLimit({
+                ...shakeLimit,
+                alarmFirst: e.target.value,
+              });
+              form.setFieldsValue({ shakeLimit });
+            }}
+          ></Radio.Group>
+        </>
+      )}
+    </Space>
+  );
+
+  console.log(shakeLimit);
   return (
     <PageContainer>
       <Card>
@@ -92,10 +196,58 @@ export default () => {
           </Form.Item>
           <Form.Item label={'触发方式'}>
             <Form.Item name={['trigger', 'type']}>
-              <TriggerWay />
+              <TriggerWay onSelect={setTriggerType} />
+            </Form.Item>
+            {triggerType === TriggerWayType.timing && (
+              <Form.Item name={['trigger', 'timer']}>
+                <TimingTrigger />
+              </Form.Item>
+            )}
+            {triggerType === TriggerWayType.device && <TriggerDevice form={form} />}
+          </Form.Item>
+          {triggerType === TriggerWayType.device && (
+            <Form.Item label={AntiShake}>
+              <TriggerTerm
+                ref={triggerRef}
+                params={requestParams}
+                // value={triggerValue}
+              />
             </Form.Item>
+          )}
+          <Form.Item hidden name={'parallel'} initialValue={false}>
+            <Input />
           </Form.Item>
-          <Form.Item label={'执行动作'}>
+          <Form.Item
+            label={
+              <>
+                <span>
+                  执行动作<span style={{ color: 'red', margin: '0 4px' }}>*</span>
+                </span>
+                <Tooltip
+                  title={
+                    <div>
+                      <div>串行:满足所有执行条件才会触发执行动作</div>
+                      <div>并行:满足任意条件时会触发执行动作</div>
+                    </div>
+                  }
+                >
+                  <QuestionCircleOutlined style={{ margin: '0 8px', fontSize: 14 }} />
+                </Tooltip>
+                <Radio.Group
+                  value={parallel}
+                  options={[
+                    { label: '串行', value: false },
+                    { label: '并行', value: true },
+                  ]}
+                  optionType="button"
+                  onChange={(e) => {
+                    setParallel(e.target.value);
+                    form.setFieldsValue({ parallel: e.target.value });
+                  }}
+                ></Radio.Group>
+              </>
+            }
+          >
             <Form.List name="actions">
               {(fields, { add, remove }) => (
                 <>
@@ -104,8 +256,9 @@ export default () => {
                       key={key}
                       form={form}
                       restField={restField}
-                      onRemove={() => remove(name)}
                       name={name}
+                      triggerType={triggerType}
+                      onRemove={() => remove(name)}
                     />
                   ))}
                   <Form.Item>
@@ -117,48 +270,49 @@ export default () => {
               )}
             </Form.List>
           </Form.Item>
+          <Form.Item label={'说明'} name={'description'}>
+            <Input.TextArea showCount maxLength={200} placeholder={'请输入说明'} />
+          </Form.Item>
+          <Form.Item hidden name={'shakeLimit'}>
+            <Input />
+          </Form.Item>
+          <Form.Item hidden name={'id'}>
+            <Input />
+          </Form.Item>
         </Form>
         <PermissionButton isPermission={getOtherPermission(['add', 'update'])} onClick={saveData}>
           保存
         </PermissionButton>
-        <Button
-          onClick={() => {
-            setTriggerValue({
-              trigger: [
-                {
-                  terms: [
-                    {
-                      column: '_now',
-                      termType: 'eq',
-                      source: 'manual',
-                      value: '2022-04-21 14:26:04',
-                    },
-                  ],
-                },
-                {
-                  terms: [
-                    {
-                      column: 'properties.test-zhibioa.recent',
-                      termType: 'lte',
-                      source: 'metrics',
-                      value: '123',
-                    },
-                  ],
-                },
-              ],
-            });
-          }}
-        >
-          设置
-        </Button>
-      </Card>
-      <Card>
-        <TriggerTerm
-          ref={triggerRef}
-          params={requestParams}
-          value={triggerValue}
-          onChange={console.log}
-        />
+        {/*<Button*/}
+        {/*  onClick={() => {*/}
+        {/*    setTriggerValue({*/}
+        {/*      trigger: [*/}
+        {/*        {*/}
+        {/*          terms: [*/}
+        {/*            {*/}
+        {/*              column: '_now',*/}
+        {/*              termType: 'eq',*/}
+        {/*              source: 'manual',*/}
+        {/*              value: '2022-04-21 14:26:04',*/}
+        {/*            },*/}
+        {/*          ],*/}
+        {/*        },*/}
+        {/*        {*/}
+        {/*          terms: [*/}
+        {/*            {*/}
+        {/*              column: 'properties.test-zhibioa.recent',*/}
+        {/*              termType: 'lte',*/}
+        {/*              source: 'metrics',*/}
+        {/*              value: '123',*/}
+        {/*            },*/}
+        {/*          ],*/}
+        {/*        },*/}
+        {/*      ],*/}
+        {/*    });*/}
+        {/*  }}*/}
+        {/*>*/}
+        {/*  设置*/}
+        {/*</Button>*/}
       </Card>
     </PageContainer>
   );

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

@@ -1,10 +1,266 @@
-import { Form } from 'antd';
-import TriggerWay from '../components/TriggerWay';
+import { useCallback, useEffect, useState } from 'react';
+import { Col, Form, Row, Select, Space, TreeSelect } from 'antd';
+import type { FormInstance } from 'antd';
+import { TimingTrigger } from '@/pages/rule-engine/Scene/Save/components';
+import { getProductList } from '@/pages/rule-engine/Scene/Save/action/device/service';
+import { queryOrgTree, querySelector } from '@/pages/rule-engine/Scene/Save/trigger/service';
+import Device from '@/pages/rule-engine/Scene/Save/action/device/deviceModal';
+import FunctionCall from '@/pages/rule-engine/Scene/Save/action/device/functionCall';
+import Operation from './operation';
+
+interface TriggerProps {
+  value?: string;
+  onChange?: (type: string) => void;
+  form?: FormInstance;
+}
+
+enum OperatorEnum {
+  'online' = 'online',
+  'offline' = 'offline',
+  'reportEvent' = 'reportEvent',
+  'reportProperty' = 'reportProperty',
+  'readProperty' = 'readProperty',
+  'writeProperty' = 'writeProperty',
+  'invokeFunction' = 'invokeFunction',
+}
+
+export default (props: TriggerProps) => {
+  const [productList, setProductList] = useState<any[]>([]);
+  const [productId, setProductId] = useState('');
+  const [selector, setSelector] = useState('fixed');
+
+  const [selectorOptions, setSelectorOptions] = useState<any[]>([]);
+  const [operation, setOperation] = useState<string | undefined>(undefined);
+  const [operatorOptions, setOperatorOptions] = useState<any[]>([]);
+
+  const [properties, setProperties] = useState<any[]>([]); // 属性
+  const [events, setEvents] = useState([]); // 事件
+  const [functions, setFunctions] = useState([]); // 功能列表
+
+  const [functionItem, setFunctionItem] = useState<any[]>([]); // 单个功能-属性列表
+  const [orgTree, setOrgTree] = useState<any>([]);
+
+  const getProducts = async () => {
+    const resp = await getProductList({ paging: false });
+    if (resp && resp.status === 200) {
+      setProductList(resp.result);
+    }
+  };
+
+  const getSelector = () => {
+    querySelector().then((resp) => {
+      if (resp.status === 200) {
+        setSelectorOptions(resp.result);
+      }
+    });
+  };
+
+  const getOrgTree = useCallback(() => {
+    queryOrgTree(productId).then((resp) => {
+      if (resp.status === 200) {
+        setOrgTree(resp.result);
+      }
+    });
+  }, [queryOrgTree]);
+
+  const handleMetadata = (metadata?: string) => {
+    try {
+      const metadataObj = JSON.parse(metadata || '{}');
+      let newOperator: any[] = [
+        { label: '设备上线', value: OperatorEnum.online },
+        { label: '设备离线', value: OperatorEnum.offline },
+      ];
+      if (metadataObj.properties && metadataObj.properties.length) {
+        newOperator = [
+          ...newOperator,
+          { label: '属性上报', value: OperatorEnum.reportProperty },
+          { label: '属性读取', value: OperatorEnum.readProperty },
+          { label: '修改属性', value: OperatorEnum.writeProperty },
+        ];
+        setProperties(metadataObj.properties);
+      }
+      if (metadataObj.events && metadataObj.events.length) {
+        newOperator = [...newOperator, { label: '事件上报', value: OperatorEnum.reportEvent }];
+        setEvents(metadataObj.events);
+      }
+      if (metadataObj.functions && metadataObj.functions.length) {
+        newOperator = [...newOperator, { label: '功能调用', value: OperatorEnum.invokeFunction }];
+        setFunctions(metadataObj.functions);
+      }
+      setOperatorOptions(newOperator);
+    } catch (err) {
+      console.warn('handleMetadata === ', err);
+    }
+  };
+
+  useEffect(() => {
+    getProducts();
+    getSelector();
+  }, []);
 
-export default () => {
   return (
-    <Form.Item>
-      <TriggerWay />
-    </Form.Item>
+    <div>
+      <Row>
+        <Col span={24}>
+          <Space>
+            <Form.Item name={['trigger', 'device', 'productId']}>
+              <Select
+                options={productList}
+                placeholder={'请选择产品'}
+                style={{ width: 220 }}
+                listHeight={220}
+                onChange={(key: any, node: any) => {
+                  props.form?.resetFields([['trigger', 'device', 'selector']]);
+                  props.form?.resetFields([['trigger', 'device', 'selectorValues']]);
+                  props.form?.resetFields([['trigger', 'device', 'operation', 'operator']]);
+                  props.form?.resetFields([['trigger', 'device', 'operation', 'operator']]);
+                  setOperation(undefined);
+                  setProductId(key);
+                  handleMetadata(node.metadata);
+                  if (selector === 'org') {
+                    getOrgTree();
+                  }
+                }}
+                fieldNames={{ label: 'name', value: 'id' }}
+              />
+            </Form.Item>
+            <Form.Item name={['trigger', 'device', 'selector']} initialValue={'fixed'}>
+              <Select
+                options={selectorOptions}
+                fieldNames={{ label: 'name', value: 'id' }}
+                onSelect={(key: string) => {
+                  if (key === 'org') {
+                    getOrgTree();
+                  }
+                  setSelector(key);
+                }}
+                style={{ width: 120 }}
+              />
+            </Form.Item>
+            {selector === 'fixed' && (
+              <Form.Item name={['trigger', 'device', 'selectorValues']}>
+                <Device productId={productId} />
+              </Form.Item>
+            )}
+            {selector === 'org' && (
+              <Form.Item name={['trigger', 'device', 'selectorValues']}>
+                <TreeSelect
+                  treeData={orgTree}
+                  fieldNames={{ label: 'name', value: 'id' }}
+                  placeholder={'请选择部门'}
+                  style={{ width: 300 }}
+                />
+              </Form.Item>
+            )}
+            {functions.length || events.length || properties.length ? (
+              <Form.Item name={['trigger', 'device', 'operation', 'operator']}>
+                <Select
+                  placeholder={'请选择触发类型'}
+                  options={operatorOptions}
+                  style={{ width: 140 }}
+                  onSelect={setOperation}
+                />
+              </Form.Item>
+            ) : null}
+          </Space>
+        </Col>
+        {operation === OperatorEnum.invokeFunction || operation === OperatorEnum.writeProperty ? (
+          <Col>
+            <Form.Item name={['trigger', 'device', 'operation', 'timer']}>
+              <TimingTrigger />
+            </Form.Item>
+          </Col>
+        ) : null}
+      </Row>
+      {operation === OperatorEnum.invokeFunction && (
+        <>
+          <Row>
+            <Col span={12}>
+              <Form.Item name={['trigger', 'device', 'operation', 'functionId']}>
+                <Select
+                  options={functions}
+                  fieldNames={{
+                    label: 'name',
+                    value: 'id',
+                  }}
+                  placeholder={'请选择功能'}
+                  onSelect={(_: any, data: any) => {
+                    const _properties = data.valueType ? data.valueType.properties : data.inputs;
+                    const array = [];
+                    for (const datum of _properties) {
+                      array.push({
+                        id: datum.id,
+                        name: datum.name,
+                        type: datum.valueType ? datum.valueType.type : '-',
+                        format: datum.valueType ? datum.valueType.format : undefined,
+                        options: datum.valueType ? datum.valueType.elements : undefined,
+                        value: undefined,
+                      });
+                    }
+                    setFunctionItem(array);
+                  }}
+                />
+              </Form.Item>
+            </Col>
+            <Col span={8}>
+              <span style={{ margin: '0 12px', lineHeight: '32px' }}>
+                定时调用所选功能,功能返回值用于条件配置
+              </span>
+            </Col>
+          </Row>
+          <Row>
+            <Col span={24}>
+              <Form.Item name={['trigger', 'device', 'operation', 'functionParameters']}>
+                <FunctionCall functionData={functionItem} />
+              </Form.Item>
+            </Col>
+          </Row>
+        </>
+      )}
+      {operation === OperatorEnum.writeProperty && (
+        <Row>
+          <Col span={24}>
+            <Form.Item name={['trigger', 'device', 'operation', 'writeProperties']}>
+              <Operation
+                propertiesList={properties.filter((item) => {
+                  if (item.expands) {
+                    return item.expands.type ? item.expands.type.includes('write') : false;
+                  }
+                  return false;
+                })}
+              />
+            </Form.Item>
+          </Col>
+        </Row>
+      )}
+      {operation === OperatorEnum.readProperty && (
+        <Row>
+          <Col>
+            <Form.Item name={['trigger', 'device', 'operation', 'readProperties']} noStyle>
+              <Select
+                mode={'multiple'}
+                options={properties.filter((item) => {
+                  if (item.expands) {
+                    return item.expands.type ? item.expands.type.includes('read') : false;
+                  }
+                  return false;
+                })}
+                maxTagCount={0}
+                maxTagPlaceholder={(values) => {
+                  return (
+                    <div style={{ maxWidth: 'calc(100% - 8px)' }}>
+                      {values.map((item) => item.label).toString()}
+                    </div>
+                  );
+                }}
+                style={{ width: 300 }}
+                fieldNames={{ label: 'name', value: 'id' }}
+              />
+            </Form.Item>
+            <span style={{ margin: '0 12px' }}>定时读取所选属性值,用于条件配置</span>
+          </Col>
+        </Row>
+      )}
+    </div>
   );
 };

+ 81 - 0
src/pages/rule-engine/Scene/Save/trigger/operation.tsx

@@ -0,0 +1,81 @@
+import { Col, Row, Select } from 'antd';
+import { useEffect, useState } from 'react';
+import FunctionCall from '@/pages/rule-engine/Scene/Save/action/device/functionCall';
+
+interface OperatorProps {
+  propertiesList?: any[];
+  value?: any;
+  onChange?: (value: any) => void;
+}
+
+export default (props: OperatorProps) => {
+  const [data, setData] = useState<any>({});
+  const [key, setKey] = useState('');
+  const [propertiesItem, setPropertiesItem] = useState<any[]>([]);
+
+  const objToArray = (_data: any) => {
+    return Object.keys(_data).map((item) => {
+      return { id: item, value: _data[item] };
+    });
+  };
+
+  useEffect(() => {
+    if (props.value) {
+      const _key = Object.keys(props.value)[0];
+      setKey(_key);
+      setData(objToArray(props.value[_key]));
+    } else {
+      setData({});
+      setKey('');
+    }
+  }, [props.value]);
+
+  return (
+    <Row>
+      <Col span={24}>
+        <Select
+          options={props.propertiesList || []}
+          fieldNames={{
+            label: 'name',
+            value: 'id',
+          }}
+          style={{ width: 300 }}
+          placeholder={'请选择属性'}
+          onSelect={(id: any, _data: any) => {
+            setPropertiesItem([
+              {
+                id: _data.id,
+                name: _data.name,
+                type: _data.valueType ? _data.valueType.type : '-',
+                format: _data.valueType ? _data.valueType.format : undefined,
+                options: _data.valueType ? _data.valueType.elements : undefined,
+                value: undefined,
+              },
+            ]);
+            if (props.onChange) {
+              props.onChange({ [id]: {} });
+            }
+          }}
+        />
+        <span style={{ margin: '0 12px', lineHeight: '32px' }}>
+          定时调用所选属性,修改后的属性值用于条件配置
+        </span>
+      </Col>
+      {key && (
+        <Col span={24}>
+          <FunctionCall
+            value={[{ id: key, value: data[key] }]}
+            functionData={propertiesItem}
+            onChange={(value) => {
+              if (props.onChange) {
+                props.onChange({
+                  [value[0].name]: value[0].value,
+                });
+              }
+            }}
+          />
+        </Col>
+      )}
+    </Row>
+  );
+};

+ 27 - 0
src/pages/rule-engine/Scene/Save/trigger/service.ts

@@ -0,0 +1,27 @@
+import { request } from '@@/plugin-request/request';
+import SystemConst from '@/utils/const';
+
+export const querySelector = () =>
+  request(`${SystemConst.API_BASE}/scene/device-selectors`, {
+    method: 'GET',
+  });
+
+export const queryOrgTree = (id: string) =>
+  request(`${SystemConst.API_BASE}/organization/_all/tree`, {
+    method: 'POST',
+    data: {
+      paging: false,
+      sorts: [{ name: 'sortIndex', order: 'asc' }],
+      terms: [
+        {
+          column: 'id',
+          termType: 'assets-dim',
+          value: {
+            assetType: 'product',
+            assetIds: [id],
+            dimensionType: 'org',
+          },
+        },
+      ],
+    },
+  });

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

@@ -82,7 +82,7 @@ const TriggerTerm = (props: Props, ref: any) => {
             const treeValue = treeFilter(_data, params, 'column');
             // 找到
             const target =
-              treeValue && treeValue[0].children
+              treeValue && treeValue[0] && treeValue[0].children
                 ? treeValue[0]?.children.find((item) => item.column === params)
                 : treeValue[0];
 

+ 26 - 17
src/pages/rule-engine/Scene/index.tsx

@@ -3,7 +3,13 @@ import React, { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
 import { Badge, message } from 'antd';
-import { EditOutlined, PlayCircleOutlined, PlusOutlined, StopOutlined } from '@ant-design/icons';
+import {
+  DeleteOutlined,
+  EditOutlined,
+  PlayCircleOutlined,
+  PlusOutlined,
+  StopOutlined,
+} from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { PermissionButton, ProTableCard } from '@/components';
 import { statusMap } from '@/pages/device/Instance';
@@ -13,7 +19,7 @@ import Service from './service';
 import { useHistory } from 'umi';
 import { getMenuPathByCode } from '@/utils/menu';
 
-export const service = new Service('rule-engine/scene');
+export const service = new Service('scene');
 
 const Scene = () => {
   const intl = useIntl();
@@ -30,7 +36,7 @@ const Scene = () => {
         style={{ padding: 0 }}
         isPermission={permission.update}
         tooltip={
-          type === 'table'
+          type !== 'table'
             ? {
                 title: intl.formatMessage({
                   id: 'pages.data.option.edit',
@@ -41,7 +47,7 @@ const Scene = () => {
         }
       >
         <EditOutlined />
-        {type === 'table' &&
+        {type !== 'table' &&
           intl.formatMessage({
             id: 'pages.data.option.edit',
             defaultMessage: '编辑',
@@ -55,7 +61,7 @@ const Scene = () => {
         popConfirm={{
           title: intl.formatMessage({
             id: `pages.data.option.${
-              record.state.value !== 'notActive' ? 'disabled' : 'enabled'
+              record.state.value !== 'started' ? 'disabled' : 'enabled'
             }.tips`,
             defaultMessage: '确认禁用?',
           }),
@@ -70,21 +76,23 @@ const Scene = () => {
           },
         }}
         tooltip={
-          type === 'table'
+          type !== 'table'
             ? {
                 title: intl.formatMessage({
-                  id: 'pages.data.option.edit',
-                  defaultMessage: '编辑',
+                  id: `pages.data.option.${
+                    record.state.value !== 'started' ? 'disabled' : 'enabled'
+                  }`,
+                  defaultMessage: '启用',
                 }),
               }
             : undefined
         }
       >
-        {record.state.value !== 'notActive' ? <StopOutlined /> : <PlayCircleOutlined />}
-        {type === 'table' &&
+        {record.state.value !== 'started' ? <StopOutlined /> : <PlayCircleOutlined />}
+        {type !== 'table' &&
           intl.formatMessage({
-            id: `pages.data.option.${record.state.value !== 'notActive' ? 'disabled' : 'enabled'}`,
-            defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
+            id: `pages.data.option.${record.state.value !== 'started' ? 'disabled' : 'enabled'}`,
+            defaultMessage: record.state.value !== 'started' ? '禁用' : '启用',
           })}
       </PermissionButton>,
       <PermissionButton
@@ -95,24 +103,25 @@ const Scene = () => {
         popConfirm={{
           title: intl.formatMessage({
             id:
-              record.state.value === 'notActive'
+              record.state.value === 'started'
                 ? 'pages.data.option.remove.tips'
                 : 'pages.device.instance.deleteTip',
           }),
+          disabled: record.state.value === 'started',
           onConfirm: async () => {},
         }}
         tooltip={
-          type === 'table'
+          type !== 'table'
             ? {
                 title: intl.formatMessage({
-                  id: 'pages.data.option.edit',
-                  defaultMessage: '编辑',
+                  id: 'pages.device.instance.deleteTip',
+                  defaultMessage: '删除',
                 }),
               }
             : undefined
         }
       >
-        <EditOutlined />
+        <DeleteOutlined />
         {type === 'table' &&
           intl.formatMessage({
             id: 'pages.data.option.edit',

+ 1 - 1
src/pages/system/Department/Assets/product/bind.tsx

@@ -35,7 +35,7 @@ const Bind = observer((props: Props) => {
       dataIndex: 'name',
       title: intl.formatMessage({
         id: 'pages.table.name',
-        defaultMessage: '名',
+        defaultMessage: '名',
       }),
       search: {
         transform: (value) => ({ name$LIKE: value }),