Explorar o código

feat(merge): merge xyh

lind %!s(int64=3) %!d(string=hai) anos
pai
achega
89a0e4d8b9
Modificáronse 30 ficheiros con 624 adicións e 600 borrados
  1. BIN=BIN
      public/images/device-trigger.png
  2. BIN=BIN
      public/images/manual-trigger.png
  3. BIN=BIN
      public/images/timing-trigger.png
  4. 11 6
      src/components/TitleComponent/index.less
  5. 8 2
      src/components/TitleComponent/index.tsx
  6. 1 0
      src/components/index.ts
  7. 2 1
      src/pages/device/Instance/Detail/Diagnose/Status/index.tsx
  8. 2 0
      src/pages/device/Instance/Detail/Diagnose/Status/model.ts
  9. 19 8
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx
  10. 40 13
      src/pages/notice/Config/SyncUser/index.tsx
  11. 1 0
      src/pages/notice/Config/index.tsx
  12. 24 19
      src/pages/notice/Config/service.ts
  13. 97 31
      src/pages/rule-engine/Scene/Save/action/action.tsx
  14. 9 6
      src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx
  15. 16 7
      src/pages/rule-engine/Scene/Save/action/device/functionCall.tsx
  16. 95 53
      src/pages/rule-engine/Scene/Save/action/device/index.tsx
  17. 15 14
      src/pages/rule-engine/Scene/Save/action/device/tagModal.tsx
  18. 5 1
      src/pages/rule-engine/Scene/Save/components/DatePickerFormat/index.tsx
  19. 2 0
      src/pages/rule-engine/Scene/Save/components/InputNumber.tsx
  20. 6 1
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/index.tsx
  21. 20 3
      src/pages/rule-engine/Scene/Save/components/TriggerWay/index.less
  22. 20 8
      src/pages/rule-engine/Scene/Save/components/TriggerWay/index.tsx
  23. 9 0
      src/pages/rule-engine/Scene/Save/index.less
  24. 89 28
      src/pages/rule-engine/Scene/Save/index.tsx
  25. 53 22
      src/pages/rule-engine/Scene/Save/trigger/index.tsx
  26. 33 17
      src/pages/rule-engine/Scene/Save/trigger/operation.tsx
  27. 0 360
      src/pages/rule-engine/Scene/Save2/index.tsx
  28. 4 0
      src/pages/rule-engine/Scene/index.tsx
  29. 7 0
      src/pages/rule-engine/Scene/service.ts
  30. 36 0
      src/pages/rule-engine/Scene/typings.d.ts

BIN=BIN
public/images/device-trigger.png


BIN=BIN
public/images/manual-trigger.png


BIN=BIN
public/images/timing-trigger.png


+ 11 - 6
src/components/TitleComponent/index.less

@@ -1,15 +1,20 @@
 @import '~antd/es/style/themes/default.less';
 
 .title {
+  position: relative;
   width: 100%;
   margin-bottom: 10px;
+  padding-left: 10px;
   color: rgba(0, 0, 0, 0.8);
   font-weight: 600;
-}
 
-.title::before {
-  margin-right: 10px;
-  color: @primary-color;
-  background-color: @primary-color;
-  content: '|';
+  .title-before {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 4px;
+    height: calc(100% - 2px);
+    background-color: @primary-color;
+    border-radius: 0 3px 3px 0;
+  }
 }

+ 8 - 2
src/components/TitleComponent/index.tsx

@@ -1,11 +1,17 @@
-import type { ReactNode } from 'react';
+import type { CSSProperties, ReactNode } from 'react';
 import './index.less';
 
 interface TitleComponentProps {
   data: ReactNode | string;
+  style?: CSSProperties;
 }
 const TitleComponent = (props: TitleComponentProps) => {
-  return <div className="title">{props.data}</div>;
+  return (
+    <div className="title" style={props.style}>
+      <div className={'title-before'}></div>
+      <span>{props.data}</span>
+    </div>
+  );
 };
 
 export default TitleComponent;

+ 1 - 0
src/components/index.ts

@@ -8,3 +8,4 @@ export { default as ScreenPlayer } from './Player/ScreenPlayer';
 export { default as Modal } from './Modal';
 export { default as AIcon } from './AIcon';
 export { default as PermissionButton } from './PermissionButton';
+export { default as TitleComponent } from './TitleComponent';

+ 2 - 1
src/pages/device/Instance/Detail/Diagnose/Status/index.tsx

@@ -161,6 +161,7 @@ const Status = observer((props: Props) => {
       } else {
         service.queryProductState(InstanceModel.detail?.productId || '').then((resp) => {
           if (resp.status === 200) {
+            DiagnoseStatusModel.product = resp.result;
             if (resp.result.accessId) {
               service.queryGatewayState(resp.result.accessId).then((response: any) => {
                 if (response.status === 200) {
@@ -211,7 +212,7 @@ const Status = observer((props: Props) => {
                                         title="确认启用"
                                         onConfirm={async () => {
                                           const res = await service.startNetwork(
-                                            resp.result?.channelId,
+                                            DiagnoseStatusModel.product?.channelId,
                                           );
                                           if (res.status === 200) {
                                             message.success('操作成功!');

+ 2 - 0
src/pages/device/Instance/Detail/Diagnose/Status/model.ts

@@ -27,6 +27,7 @@ export const DiagnoseStatusModel = model<{
   };
   list: ListProps[];
   model: boolean;
+  product: any;
 }>({
   status: {
     config: {
@@ -87,4 +88,5 @@ export const DiagnoseStatusModel = model<{
     },
   ],
   model: true,
+  product: {},
 });

+ 19 - 8
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -1,7 +1,6 @@
 import React, { useContext, useEffect, useState } from 'react';
 import { Badge, Col, Form, Input, message, Pagination, Row, Select, Table } from 'antd';
 import { service } from '@/pages/device/Instance';
-import _ from 'lodash';
 import './index.less';
 
 const defaultImage = require('/public/images/metadata-map.png');
@@ -48,7 +47,11 @@ const EditableCell = ({
   const save = async () => {
     try {
       const values = await form.validateFields();
-      handleSave({ ...record, originalId: values?.originalId });
+      handleSave({
+        ...record,
+        originalId: values?.originalId,
+        customMapping: values?.originalId !== '',
+      });
     } catch (errInfo) {
       console.log('Save failed:', errInfo);
     }
@@ -75,7 +78,7 @@ const EditableCell = ({
           }
         >
           <Select.Option value={''}>使用物模型属性</Select.Option>
-          {record.originalId !== record.metadataId && (
+          {record.customMapping && (
             <Select.Option value={record.originalId}>
               {temp?.name}({temp?.id})
             </Select.Option>
@@ -153,13 +156,21 @@ const EditableTable = (props: Props) => {
       resp = await service.queryProductMetadata(props.data.id);
     }
     if (resp.status === 200) {
-      const data = resp.result;
       const obj: any = {};
-      data.map((i: any) => {
-        obj[i?.metadataId] = i;
+      const data = (resp?.result || []).map((i: any) => {
+        const t = {
+          ...i,
+          originalId: i.customMapping ? i.originalId : '',
+        };
+        obj[i?.metadataId] = t;
+        return t;
       });
       if (lists.length > 0) {
-        setPmList(lists.filter((i) => !_.map(data, 'originalId').includes(i.id)));
+        const arr = lists.filter((i) => {
+          const t = data.find((item: any) => item?.originalId === i?.id);
+          return !t || (t && !t.customMapping);
+        });
+        setPmList(arr);
       } else {
         setPmList([]);
       }
@@ -205,7 +216,7 @@ const EditableTable = (props: Props) => {
     const newData = [...dataSource.data];
     const index = newData.findIndex((item) => row.id === item.id);
     const item = newData[index];
-    if (item?.originalId !== row?.originalId) {
+    if (item?.originalId !== row?.originalId || row.customMapping !== item.customMapping) {
       const resp = await service[
         props.type === 'device' ? 'saveDeviceMetadata' : 'saveProductMetadata'
       ](props.data?.id, [

+ 40 - 13
src/pages/notice/Config/SyncUser/index.tsx

@@ -14,6 +14,7 @@ const SyncUser = observer(() => {
   const id = (location as any).query?.id;
   const [visible, setVisible] = useState<boolean>(false);
   const [current, setCurrent] = useState<any>({});
+  const [list, setList] = useState<any[]>([]);
 
   const idMap = {
     dingTalk: '钉钉',
@@ -24,19 +25,14 @@ const SyncUser = observer(() => {
 
   const columns: ProColumns<any>[] = [
     {
-      dataIndex: 'id',
+      dataIndex: 'thirdPartyUserName',
       title: `${idMap[id]}用户名`,
-      render: (text: any, record: any) => (
-        <span>
-          {text}({record?.name})
-        </span>
-      ),
     },
     {
       dataIndex: 'userId',
       title: `用户`,
       render: (text: any, record: any) => (
-        <span>{record?.userId ? `${record?.username}(${record?.userName})` : '--'}</span>
+        <span>{record?.userId ? `${record?.userName}(${record?.username})` : '--'}</span>
       ),
     },
     {
@@ -44,8 +40,8 @@ const SyncUser = observer(() => {
       title: '绑定状态',
       render: (text: any, record: any) => (
         <Badge
-          status={record?.userId ? 'success' : 'error'}
-          text={record?.userId ? '已绑定' : '未绑定'}
+          status={record?.status === 1 ? 'success' : 'error'}
+          text={record?.status === 1 ? '已绑定' : '未绑定'}
         />
       ),
     },
@@ -65,13 +61,15 @@ const SyncUser = observer(() => {
           </Button>
         </Tooltip>,
         <Tooltip title={'解绑用户'} key="unbind">
-          {record?.userId && (
+          {record?.status === 1 && (
             <Button type="link">
               <Popconfirm
                 title={'确认解绑'}
                 onConfirm={async () => {
                   if (record?.bindingId) {
-                    const resp = await service.syncUser.unBindUser(record.bindingId);
+                    const resp = await service.syncUser.unBindUser(record.bindingId, {
+                      bindingId: record.bindingId,
+                    });
                     if (resp.status === 200) {
                       message.success('操作成功!');
                       actionRef.current?.reload();
@@ -139,6 +137,7 @@ const SyncUser = observer(() => {
                 title: 'name',
                 key: 'id',
               }}
+              selectedKeys={[dept || '']}
               onSelect={(key) => {
                 setDept(key[0] as string);
               }}
@@ -149,7 +148,7 @@ const SyncUser = observer(() => {
         <Col span={20}>
           {dept && (
             <ProTable
-              rowKey="id"
+              rowKey="thirdPartyUserId"
               actionRef={actionRef}
               search={false}
               columns={columns}
@@ -167,6 +166,7 @@ const SyncUser = observer(() => {
                     params.dept || '',
                   )
                   .then((resp: any) => {
+                    setList(resp);
                     return {
                       code: '',
                       result: {
@@ -179,7 +179,34 @@ const SyncUser = observer(() => {
                     };
                   })
               }
-              headerTitle={<Button>保存</Button>}
+              headerTitle={
+                <Popconfirm
+                  title="确认保存"
+                  onConfirm={async () => {
+                    const arr = list
+                      .filter((item) => item.status === 0)
+                      .map((i) => {
+                        return {
+                          userId: i.userId,
+                          providerName: i.userName,
+                          thirdPartyUserId: i.thirdPartyUserId,
+                        };
+                      });
+                    const resp = await service.syncUser.bindUser(
+                      id,
+                      state.current?.provider || '',
+                      state.current?.id || '',
+                      [...arr],
+                    );
+                    if (resp.status === 200) {
+                      message.success('操作成功!');
+                      actionRef.current?.reload();
+                    }
+                  }}
+                >
+                  <Button type="primary">保存</Button>
+                </Popconfirm>
+              }
             />
           )}
         </Col>

+ 1 - 0
src/pages/notice/Config/index.tsx

@@ -300,6 +300,7 @@ const Config = observer(() => {
                 <PermissionButton
                   key="syncUser"
                   isPermission={true}
+                  type="link"
                   onClick={() => {
                     state.syncUser = true;
                     state.current = record;

+ 24 - 19
src/pages/notice/Config/service.ts

@@ -71,9 +71,10 @@ class Service extends BaseService<ConfigItem> {
       }),
     getUserBindInfo: () =>
       request(`${SystemConst.API_BASE}/user/third-party/me`, { method: 'GET' }),
-    unBindUser: (bindId: string) =>
-      request(`${SystemConst.API_BASE}/user/third-party/me/${bindId}`, {
-        method: 'DELETE',
+    unBindUser: (bindingId: string, data: any) =>
+      request(`${SystemConst.API_BASE}/user/third-party/${bindingId}/_unbind`, {
+        method: 'POST',
+        data,
       }),
   };
 
@@ -93,26 +94,30 @@ class Service extends BaseService<ConfigItem> {
         map((resp) => resp.map((i) => i.result)),
         mergeMap((res) => {
           const [resp1, resp2, resp3] = res;
-          const list = resp1.map((item: { id: string; name: string }) => {
-            const data =
-              resp2.find(
-                (i: { userId: string; providerName: string; thirdPartyUserId: string }) =>
-                  i.thirdPartyUserId === item.id,
-              ) || {};
-            let _user: Partial<UserItem> = {};
-            if (data) {
-              _user = resp3.find((i: UserItem) => i.id === data.userId);
+          // 1.自动匹配
+          // 2.已匹配
+          // status: 1: 已匹配 0: 自动匹配,但是没有保存 -1: 未匹配
+          const arr = resp1.map((item: { id: string; name: string }) => {
+            let user = resp3.find((i: UserItem) => i?.name === item?.name);
+            const thirdPartyUser = resp2.find(
+              (i: { userId: string; providerName: string; thirdPartyUserId: string }) =>
+                i?.thirdPartyUserId === item?.id,
+            );
+            if (thirdPartyUser) {
+              user = resp3.find((i: UserItem) => i?.id === thirdPartyUser?.userId);
             }
+            const status = thirdPartyUser ? 1 : user ? 0 : -1;
             return {
-              ..._user,
-              ...data,
-              ...item,
-              bindingId: data?.id,
-              userId: _user?.id,
-              userName: _user?.name,
+              thirdPartyUserId: item?.id,
+              thirdPartyUserName: item?.name,
+              bindingId: thirdPartyUser?.id,
+              userId: user?.id,
+              userName: user?.name,
+              username: user?.username,
+              status,
             };
           });
-          return list;
+          return arr;
         }),
         toArray(),
       ),

+ 97 - 31
src/pages/rule-engine/Scene/Save/action/action.tsx

@@ -1,6 +1,6 @@
 import type { FormInstance } from 'antd';
 import { Button, Form, Select } from 'antd';
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
 import { useRequest } from 'umi';
 import {
   queryMessageConfig,
@@ -14,6 +14,8 @@ import WriteProperty from './device/WriteProperty';
 import ReadProperty from './device/readProperty';
 import FunctionCall from './device/functionCall';
 import { InputNumber } from '../components';
+import { DeleteOutlined } from '@ant-design/icons';
+import { observer } from '@formily/reactive-react';
 
 interface ActionProps {
   restField: any;
@@ -22,14 +24,16 @@ interface ActionProps {
   title?: string;
   triggerType: string;
   onRemove: () => void;
+  actionItemData?: any;
 }
 
-const ActionItem = (props: ActionProps) => {
+export default observer((props: ActionProps) => {
   const { name } = props;
   const [type1, setType1] = useState('');
   // 消息通知
   const [notifyType, setNotifyType] = useState('');
   const [configId, setConfigId] = useState('');
+  const [templateId, setTemplateId] = useState('');
   const [templateData, setTemplateData] = useState<any>(undefined);
   // 设备输出
   const [deviceMessageType, setDeviceMessageType] = useState('WRITE_PROPERTY');
@@ -59,22 +63,95 @@ const ActionItem = (props: ActionProps) => {
     formatResult: (res) => res.result,
   });
 
+  const handleNotifier = async (list: any[], data: any) => {
+    if (data.notify.notifierId) {
+      // 通知配置
+      setConfigId(data.notify.notifierId);
+      const notifierItem = list.find((item: any) => item.id === data.notify.notifierId);
+      if (notifierItem) {
+        await queryMessageTemplates({
+          terms: [
+            { column: 'type', value: data.notify.notifyType },
+            { column: 'provider', value: notifierItem.provider },
+          ],
+        });
+      }
+    }
+  };
+
+  useEffect(() => {
+    if (props.actionItemData && messageConfig) {
+      handleNotifier(messageConfig, props.actionItemData);
+    }
+  }, [configId, messageConfig]);
+
+  useEffect(() => {
+    if (
+      props.actionItemData &&
+      props.actionItemData.notify &&
+      props.actionItemData.notify.notifyType
+    ) {
+      queryMessageConfigs({
+        terms: [{ column: 'type$IN', value: props.actionItemData.notify.notifyType }],
+      }).then(async (resp) => {
+        if (props.actionItemData.notify.notifierId) {
+          await handleNotifier(resp, props.actionItemData);
+        }
+      });
+    }
+  }, [notifyType]);
+
+  useEffect(() => {
+    const data = props.actionItemData;
+    if (data && data.notify && data.notify.templateId) {
+      queryMessageTemplateDetail(data.notify.templateId).then((resp) => {
+        if (resp.status === 200) {
+          setTemplateData(resp.result);
+        }
+      });
+    }
+  }, [templateId]);
+
+  const handleInit = useCallback(async (data: any) => {
+    if (data) {
+      if (data.executor) {
+        setType1(data.executor);
+      }
+
+      if (data.notify) {
+        // 消息通知
+        if (data.notify.notifyType) {
+          setNotifyType(data.notify.notifyType);
+        }
+
+        if (data.notify.notifierId) {
+          setConfigId(data.notify.notifierId);
+        }
+
+        if (data.notify.templateId) {
+          // 通知模板
+          setTemplateId(data.notify.templateId);
+        }
+      }
+    }
+  }, []);
+
+  useEffect(() => {
+    handleInit(props.actionItemData);
+  }, [props.actionItemData]);
+
   const MessageNodes = (
     <>
-      <Form.Item {...props.restField} name={[name, 'notify', 'type']}>
+      <Form.Item {...props.restField} name={[name, 'notify', 'notifyType']}>
         <Select
           options={messageType}
           fieldNames={{ value: 'id', label: 'name' }}
           placeholder={'请选择通知方式'}
           style={{ width: 140 }}
-          onChange={async (key: string) => {
+          onChange={async () => {
             setTemplateData(undefined);
-            setNotifyType(key);
             props.form.resetFields([['actions', name, 'notify', 'notifierId']]);
             props.form.resetFields([['actions', name, 'notify', 'templateId']]);
-            await queryMessageConfigs({
-              terms: [{ column: 'type$IN', value: key }],
-            });
           }}
         />
       </Form.Item>
@@ -83,17 +160,9 @@ const ActionItem = (props: ActionProps) => {
           options={messageConfig}
           loading={messageConfigLoading}
           fieldNames={{ value: 'id', label: 'name' }}
-          onChange={async (key: string, node: any) => {
-            setConfigId(key);
+          onChange={() => {
             setTemplateData(undefined);
             props.form.resetFields([['actions', name, 'notify', 'templateId']]);
-
-            await queryMessageTemplates({
-              terms: [
-                { column: 'type', value: notifyType },
-                { column: 'provider', value: node.provider },
-              ],
-            });
           }}
           style={{ width: 160 }}
           placeholder={'请选择通知配置'}
@@ -106,12 +175,6 @@ const ActionItem = (props: ActionProps) => {
           fieldNames={{ value: 'id', label: 'name' }}
           style={{ width: 160 }}
           placeholder={'请选择通知模板'}
-          onChange={async (key: string) => {
-            const resp = await queryMessageTemplateDetail(key);
-            if (resp.status === 200) {
-              setTemplateData(resp.result);
-            }
-          }}
         />
       </Form.Item>
     </>
@@ -127,8 +190,15 @@ const ActionItem = (props: ActionProps) => {
     <div className={'actions-item'}>
       <div className={'actions-item-title'}>
         执行动作 {props.name + 1}
-        <Button type={'link'} onClick={props.onRemove}>
-          删除
+        <Button
+          onClick={props.onRemove}
+          danger
+          style={{
+            padding: '0 8px',
+            margin: '0 0 12px 12px',
+          }}
+        >
+          <DeleteOutlined />
         </Button>
       </div>
       <div style={{ display: 'flex', gap: 12 }}>
@@ -141,14 +211,12 @@ const ActionItem = (props: ActionProps) => {
             ]}
             placeholder={'请选择动作方式'}
             style={{ width: 140 }}
-            onSelect={(key: string) => {
-              setType1(key);
-            }}
           />
         </Form.Item>
         {type1 === 'notify' && MessageNodes}
         {type1 === 'device' && (
           <DeviceSelect
+            value={props.actionItemData ? props.actionItemData.device : undefined}
             name={props.name}
             form={props.form}
             triggerType={props.triggerType}
@@ -197,6 +265,4 @@ const ActionItem = (props: ActionProps) => {
       ) : null}
     </div>
   );
-};
-
-export default ActionItem;
+});

+ 9 - 6
src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx

@@ -13,7 +13,6 @@ interface WritePropertyProps {
 }
 
 export default (props: WritePropertyProps) => {
-  console.log(props.properties);
   const [source, setSource] = useState('fixed');
   const [builtInList, setBuiltInList] = useState([]);
   const [propertiesKey, setPropertiesKey] = useState<string | undefined>(undefined);
@@ -37,17 +36,22 @@ export default (props: WritePropertyProps) => {
   }, [source, props.type]);
 
   useEffect(() => {
-    if (props.value) {
+    console.log('writeProperty', props.value);
+    if (props.value && props.properties && props.properties.length) {
       if (0 in props.value) {
         setPropertiesValue(props.value[0]);
       } else {
         Object.keys(props.value).forEach((key: string) => {
           setPropertiesKey(key);
           setPropertiesValue(props.value[key]);
+          const propertiesItem = props.properties.find((item: any) => item.id === key);
+          if (propertiesItem) {
+            setPropertiesType(propertiesItem.valueType.type);
+          }
         });
       }
     }
-  }, [props.value]);
+  }, [props.value, props.properties]);
 
   const onChange = (key?: string, value?: any) => {
     if (props.onChange) {
@@ -94,7 +98,7 @@ export default (props: WritePropertyProps) => {
             // @ts-ignore
             <DatePicker
               style={{ width: 300 }}
-              value={propertiesValue ? moment(propertiesValue) : undefined}
+              value={propertiesValue ? moment(propertiesValue, 'YYYY-MM-DD HH:mm:ss') : undefined}
               onChange={(date) => {
                 onChange(propertiesKey, date ? date.format('YYYY-MM-DD HH:mm:ss') : undefined);
               }}
@@ -121,9 +125,8 @@ export default (props: WritePropertyProps) => {
         options={props.properties}
         fieldNames={{ label: 'name', value: 'id' }}
         style={{ width: 120 }}
-        onSelect={(key: any, node: any) => {
+        onSelect={(key: any) => {
           onChange(key, undefined);
-          setPropertiesType(node.valueType.type);
         }}
         placeholder={'请选择属性'}
       ></Select>

+ 16 - 7
src/pages/rule-engine/Scene/Save/action/device/functionCall.tsx

@@ -1,10 +1,10 @@
 import type { ProColumns } from '@jetlinks/pro-table';
 import { EditableProTable } from '@jetlinks/pro-table';
-import { DatePicker, Input, InputNumber, Select } from 'antd';
+import { Input, InputNumber, Select } from 'antd';
 import React, { useEffect, useRef, useState } from 'react';
 import type { ProFormInstance } from '@ant-design/pro-form';
 import ProForm from '@ant-design/pro-form';
-import moment from 'moment';
+import { DatePickerFormat } from '@/pages/rule-engine/Scene/Save/components';
 
 type FunctionTableDataType = {
   id: string;
@@ -44,7 +44,6 @@ export default (props: FunctionCallProps) => {
         }),
       });
     }
-    console.log(props.value);
   }, []);
 
   const getItemNode = (record: any) => {
@@ -55,6 +54,7 @@ export default (props: FunctionCallProps) => {
       case 'enum':
         return (
           <Select
+            value={record.value}
             style={{ width: '100%', textAlign: 'left' }}
             options={record.options}
             fieldNames={{ label: 'text', value: 'value' }}
@@ -64,6 +64,7 @@ export default (props: FunctionCallProps) => {
       case 'boolean':
         return (
           <Select
+            value={record.value}
             style={{ width: '100%', textAlign: 'left' }}
             options={[
               { label: 'true', value: true },
@@ -76,13 +77,20 @@ export default (props: FunctionCallProps) => {
       case 'long':
       case 'float':
       case 'double':
-        return <InputNumber style={{ width: '100%' }} placeholder={'请输入' + name} />;
+        return (
+          <InputNumber
+            value={record.value}
+            style={{ width: '100%' }}
+            placeholder={'请输入' + name}
+          />
+        );
       case 'date':
         return (
           <>
             {
               // @ts-ignore
-              <DatePicker
+              <DatePickerFormat
+                value={record.value}
                 format={record.format || 'YYYY-MM-DD HH:mm:ss'}
                 style={{ width: '100%' }}
               />
@@ -90,7 +98,7 @@ export default (props: FunctionCallProps) => {
           </>
         );
       default:
-        return <Input placeholder={'请输入' + name} />;
+        return <Input value={record.value} placeholder={'请输入' + name} />;
     }
   };
 
@@ -113,6 +121,7 @@ export default (props: FunctionCallProps) => {
       align: 'center',
       width: 260,
       renderFormItem: (_, row: any) => {
+        console.log('functionCall', row.record);
         return getItemNode(row.record);
       },
     },
@@ -128,7 +137,7 @@ export default (props: FunctionCallProps) => {
           props.onChange(
             values.table.map((item: any) => ({
               name: item.id,
-              value: item.type === 'date' ? moment(item.value).format(item.format) : item.value,
+              value: item.value,
             })),
           );
         }

+ 95 - 53
src/pages/rule-engine/Scene/Save/action/device/index.tsx

@@ -9,6 +9,7 @@ interface DeviceProps {
   name: number;
   triggerType: string;
   form?: FormInstance;
+  value?: any;
   restField?: any;
   onProperties: (data: any) => void;
   onMessageTypeChange: (type: string) => void;
@@ -38,17 +39,43 @@ export default (props: DeviceProps) => {
   const [productId, setProductId] = useState<string>('');
   const [sourceList, setSourceList] = useState(DefaultSourceOptions);
   const [productList, setProductList] = useState<any[]>([]);
-  const [selector, setSelector] = useState(SourceEnum.fixed);
+  const [selector, setSelector] = useState(props.value ? props.value.selector : SourceEnum.fixed);
   const [messageType, setMessageType] = useState(MessageTypeEnum.WRITE_PROPERTY);
+  const [functionId, setFunctionId] = useState('');
   const [functionList, setFunctionList] = useState([]);
   const [tagList, setTagList] = useState([]);
 
+  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);
+    }
+  };
+
   const getProducts = async () => {
     const resp = await getProductList({ paging: false });
     if (resp && resp.status === 200) {
       setProductList(resp.result);
+      if (props.value && props.value.productId) {
+        const productItem = resp.result.find((item: any) => item.id === props.value.productId);
+        if (productItem) {
+          handleMetadata(productItem.metadata);
+        }
+      }
     }
   };
+
   useEffect(() => {
     props.form?.resetFields([['actions', name, 'device', 'selector']]);
     if (props.triggerType === 'device') {
@@ -59,26 +86,69 @@ export default (props: DeviceProps) => {
   }, [props.triggerType]);
 
   useEffect(() => {
+    if (productId && productList.length) {
+      const productItem = productList.find((item: any) => item.id === props.value.productId);
+      if (productItem) {
+        handleMetadata(productItem.metadata);
+      }
+    }
+  }, [productId]);
+
+  useEffect(() => {
+    if (functionId && functionList.length) {
+      const functionItem: any = functionList.find((item: any) => item.id === functionId);
+      if (functionItem) {
+        const properties = functionItem.valueType
+          ? functionItem.valueType.properties
+          : functionItem.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);
+        }
+      }
+    }
+  }, [functionId, functionList]);
+
+  useEffect(() => {
     getProducts();
   }, []);
 
-  const handleMetadata = (metadata?: string) => {
-    try {
-      const metadataObj = JSON.parse(metadata || '{}');
-      if (props.onProperties) {
-        props.onProperties(metadataObj.properties || []);
+  useEffect(() => {
+    console.log('actions-device', props.value);
+    const deviceData = props.value;
+    if (deviceData) {
+      if (deviceData.productId) {
+        setProductId(deviceData.productId);
       }
-      if (!metadataObj.functions) {
-        if (props.onFunctionChange) {
-          props.onFunctionChange([]);
+
+      if (deviceData.selector) {
+        setSelector(deviceData.selector);
+      }
+
+      if (deviceData.message) {
+        if (deviceData.message.messageType) {
+          if (props.onMessageTypeChange) {
+            props.onMessageTypeChange(deviceData.message.messageType);
+          }
+          setMessageType(deviceData.message.messageType);
+        }
+
+        if (deviceData.message.functionId) {
+          setFunctionId(deviceData.message.functionId);
         }
       }
-      setTagList(metadataObj.tags || []);
-      setFunctionList(metadataObj.functions || []);
-    } catch (err) {
-      console.warn('handleMetadata === ', err);
     }
-  };
+  }, [props.value]);
 
   return (
     <>
@@ -88,48 +158,43 @@ export default (props: DeviceProps) => {
           placeholder={'请选择产品'}
           style={{ width: 220 }}
           listHeight={220}
-          onChange={(key: any, node: any) => {
+          onChange={() => {
             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}
+        initialValue={props.value ? props.value.selector : SourceEnum.fixed}
       >
-        <Select
-          options={sourceList}
-          style={{ width: 120 }}
-          onChange={(key) => {
-            setSelector(key);
-          }}
-        />
+        <Select options={sourceList} style={{ width: 120 }} />
       </Form.Item>
       {selector === SourceEnum.fixed && (
-        <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+        <Form.Item name={[name, 'device', 'selectorValues']}>
           <Device productId={productId} />
         </Form.Item>
       )}
       {selector === SourceEnum.tag && (
-        <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+        <Form.Item name={[name, 'device', 'selectorValues']}>
           <TagModal tagData={tagList} />
         </Form.Item>
       )}
       {selector === SourceEnum.relation && (
-        <Form.Item name={[name, 'device', 'selectorValues']} {...props.restField}>
+        <Form.Item name={[name, 'device', 'selectorValues']}>
           <Select style={{ width: 300 }} />
         </Form.Item>
       )}
       <Form.Item
         name={[name, 'device', 'message', 'messageType']}
-        initialValue={MessageTypeEnum.WRITE_PROPERTY}
+        initialValue={
+          props.value && props.value.message && props.value.message.messageType
+            ? props.value.message.messageType
+            : MessageTypeEnum.WRITE_PROPERTY
+        }
         {...props.restField}
       >
         <Select
@@ -138,12 +203,6 @@ export default (props: DeviceProps) => {
             { 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>
@@ -154,23 +213,6 @@ export default (props: DeviceProps) => {
             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}

+ 15 - 14
src/pages/rule-engine/Scene/Save/action/device/tagModal.tsx

@@ -3,15 +3,9 @@ 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[];
+  value?: any[];
   onChange?: (value: any[]) => void;
 }
 
@@ -46,18 +40,25 @@ export default (props: TagModalProps) => {
   }, [visible, props.tagData]);
 
   useEffect(() => {
-    if (props.value) {
+    if (
+      props.value &&
+      props.value[0] &&
+      props.value[0].name &&
+      props.tagData &&
+      props.tagData.length
+    ) {
       const names: string[] = [];
-      const newTagList = props.value
-        .filter((valueItem) => {
+      const newTagList = props.value[0].value
+        .filter((valueItem: any) => {
           return props.tagData.some((item) => valueItem.column === item.id);
         })
-        .map((valueItem) => {
+        .map((valueItem: any) => {
           const oldItem = props.tagData.find((item) => item.id === valueItem.column);
           if (oldItem) {
             names.push(oldItem.name);
             return {
               ...handleItem(oldItem),
+              value: valueItem.value,
               type: valueItem.type,
             };
           }
@@ -68,7 +69,7 @@ export default (props: TagModalProps) => {
     } else {
       setTagList([{}]);
     }
-  }, [props.value]);
+  }, [props.value, props.tagData]);
 
   const getItemNode = (record: any) => {
     const type = record.valueType;
@@ -123,7 +124,7 @@ export default (props: TagModalProps) => {
             {
               // @ts-ignore
               <DatePicker
-                value={record.value && moment(record.value)}
+                value={record.value && moment(record.value, 'YYYY-MM-DD HH:mm:ss')}
                 format={record.format || 'YYYY-MM-DD HH:mm:ss'}
                 style={{ width: '100%' }}
                 onChange={(_, date) => {
@@ -163,7 +164,7 @@ export default (props: TagModalProps) => {
               };
             });
           if (props.onChange) {
-            props.onChange(newValue);
+            props.onChange([{ value: newValue, name: '标签' }]);
           }
           setVisible(false);
           setTagList([{}]);

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

@@ -15,7 +15,11 @@ export default (props: DatePickerFormat) => {
         // @ts-ignore
         <DatePicker
           {...extraProps}
-          value={typeof value === 'string' ? moment(value) : value}
+          value={
+            value
+              ? moment(value, props.format ? (props.format as string) : 'YYYY-MM-DD HH:mm:ss')
+              : undefined
+          }
           onChange={(date, dateString) => {
             if (onChange) {
               onChange(dateString, date);

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

@@ -11,6 +11,7 @@ export default (props: InputNumberProps) => {
   const [unit, setUnit] = useState(props.value?.unit || 'seconds');
 
   useEffect(() => {
+    console.log('timer', props.value);
     setTime(props.value?.time || 0);
     setUnit(props.value?.unit || 'seconds');
   }, [props.value]);
@@ -36,6 +37,7 @@ export default (props: InputNumberProps) => {
 
   return (
     <InputNumber
+      value={time}
       addonAfter={TimeTypeAfter}
       style={{ width: 150 }}
       min={0}

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

@@ -3,6 +3,7 @@ import { TimeSelect } from '@/pages/rule-engine/Scene/Save/components';
 import { useCallback, useEffect, useState } from 'react';
 import { omit } from 'lodash';
 import moment from 'moment';
+import classNames from 'classnames';
 
 type TimerType = {
   trigger: string;
@@ -21,6 +22,7 @@ type TimerType = {
 };
 
 interface TimingTrigger {
+  className?: string;
   value?: TimerType;
   onChange?: (value: TimerType) => void;
 }
@@ -177,7 +179,10 @@ export default (props: TimingTrigger) => {
     );
 
   return (
-    <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
+    <div
+      style={{ display: 'flex', gap: 12, alignItems: 'center' }}
+      className={classNames(props.className)}
+    >
       <Select
         options={[
           { label: '按周', value: TriggerEnum.week },

+ 20 - 3
src/pages/rule-engine/Scene/Save/components/TriggerWay/index.less

@@ -2,22 +2,39 @@
 
 .trigger-way-warp {
   display: flex;
-  gap: 20px;
+  gap: 24px;
   width: 100%;
 
   .trigger-way-item {
-    padding: 20px 16px;
+    display: flex;
+    padding: 22px 16px;
     border: 1px solid #e0e4e8;
     border-radius: 2px;
     cursor: pointer;
     transition: all 0.3s;
 
+    .way-item-title {
+      p {
+        margin-bottom: 8px;
+        font-weight: bold;
+        font-size: 16px;
+      }
+
+      span {
+        color: rgba(#000, 0.24);
+        font-size: 12px;
+      }
+    }
+
+    .way-item-image {
+      margin-left: 26px;
+    }
+
     &:hover {
       color: @primary-color-hover;
     }
 
     &.active {
-      color: @primary-color-active;
       border-color: @primary-color-active;
     }
   }

+ 20 - 8
src/pages/rule-engine/Scene/Save/components/TriggerWay/index.tsx

@@ -4,6 +4,7 @@ import './index.less';
 
 interface TriggerWayProps {
   value?: string;
+  className?: string;
   onChange?: (type: string) => void;
   onSelect?: (type: string) => void;
 }
@@ -19,22 +20,21 @@ export default (props: TriggerWayProps) => {
 
   useEffect(() => {
     setType(props.value || '');
+    if (props.onSelect) {
+      props.onSelect(props.value || '');
+    }
   }, [props.value]);
 
   const onSelect = (_type: string) => {
     setType(_type);
 
-    if (props.onSelect) {
-      props.onSelect(_type);
-    }
-
     if (props.onChange) {
       props.onChange(_type);
     }
   };
 
   return (
-    <div className={'trigger-way-warp'}>
+    <div className={classNames('trigger-way-warp', props.className)}>
       <div
         className={classNames('trigger-way-item', {
           active: type === TriggerWayType.manual,
@@ -43,7 +43,11 @@ export default (props: TriggerWayProps) => {
           onSelect(TriggerWayType.manual);
         }}
       >
-        手动触发
+        <div className={'way-item-title'}>
+          <p>手动触发</p>
+          <span>MANUAL TRIGGER</span>
+        </div>
+        <img className={'way-item-image'} src={'/images/manual-trigger.png'} />
       </div>
       <div
         className={classNames('trigger-way-item', {
@@ -53,7 +57,11 @@ export default (props: TriggerWayProps) => {
           onSelect(TriggerWayType.timing);
         }}
       >
-        定时触发
+        <div className={'way-item-title'}>
+          <p>定时触发</p>
+          <span>TIMING TRIGGER</span>
+        </div>
+        <img className={'way-item-image'} src={'/images/timing-trigger.png'} />
       </div>
       <div
         className={classNames('trigger-way-item', {
@@ -63,7 +71,11 @@ export default (props: TriggerWayProps) => {
           onSelect(TriggerWayType.device);
         }}
       >
-        设备触发
+        <div className={'way-item-title'}>
+          <p>设备触发</p>
+          <span>DEVICE TRIGGER</span>
+        </div>
+        <img className={'way-item-image'} src={'/images/device-trigger.png'} />
       </div>
     </div>
   );

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

@@ -0,0 +1,9 @@
+@bgColor: #fafafa;
+
+.scene-save {
+  .trigger-type-content,
+  & .scene-actions {
+    padding: 24px;
+    background-color: @bgColor;
+  }
+}

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

@@ -13,7 +13,7 @@ import {
 } from 'antd';
 import { useLocation } from 'umi';
 import { useEffect, useRef, useState } from 'react';
-import { PermissionButton } from '@/components';
+import { PermissionButton, TitleComponent } from '@/components';
 import ActionItems from './action/action';
 import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
 import { TimingTrigger, TriggerWay } from './components';
@@ -21,6 +21,10 @@ import { TriggerWayType } from './components/TriggerWay';
 import TriggerTerm from '@/pages/rule-engine/Scene/TriggerTerm';
 import TriggerDevice from './trigger';
 import { service } from '../index';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import './index.less';
+import { model } from '@formily/reactive';
+import type { FormModelType } from '@/pages/rule-engine/Scene/typings';
 
 type ShakeLimitType = {
   enabled: boolean;
@@ -30,30 +34,45 @@ type ShakeLimitType = {
   alarmFirst?: boolean;
 };
 
+const DefaultShakeLimit = {
+  enabled: false,
+  alarmFirst: true,
+};
+
+export let FormModel = model<FormModelType>({});
+
 export default () => {
   const location = useLocation();
   const [form] = Form.useForm();
+  const intl = useIntl();
   const triggerRef = useRef<any>();
 
   const { getOtherPermission } = PermissionButton.usePermission('rule-engine/Scene');
   const [triggerType, setTriggerType] = useState('');
   // const [triggerValue, setTriggerValue] = useState<any>();
+  const [loading, setLoading] = useState(false);
   const [parallel, setParallel] = useState(false); // 是否并行
-  const [shakeLimit, setShakeLimit] = useState<ShakeLimitType>({
-    enabled: false,
-    alarmFirst: true,
-  });
+  const [shakeLimit, setShakeLimit] = useState<ShakeLimitType>(DefaultShakeLimit);
   const [requestParams, setRequestParams] = useState<any>(undefined);
+  const [formDatas, setFormDatas] = useState({});
 
-  const getDetail = async () => {
-    // TODO 回显数据
+  const getDetail = async (id: string) => {
+    const resp = await service.detail(id);
+    if (resp.status === 200 && resp.result) {
+      const _data: any = resp.result;
+      console.log(_data);
+      FormModel = _data;
+      form.setFieldsValue(_data);
+      setParallel(_data.parallel);
+      setShakeLimit(_data.shakeLimit || DefaultShakeLimit);
+    }
   };
 
   useEffect(() => {
     const params = new URLSearchParams(location.search);
     const id = params.get('id');
     if (id) {
-      getDetail();
+      getDetail(id);
     }
   }, [location]);
 
@@ -71,7 +90,9 @@ export default () => {
     }
     console.log(formData);
     if (formData) {
-      const resp = formData.id ? await service.update(formData) : await service.save(formData);
+      setLoading(true);
+      const resp = formData.id ? await service.updateScene(formData) : await service.save(formData);
+      setLoading(false);
       if (resp.status === 200) {
         message.success('操作成功');
       } else {
@@ -82,7 +103,7 @@ export default () => {
 
   const AntiShake = (
     <Space>
-      <span>触发条件</span>
+      <TitleComponent data={'触发条件'} style={{ margin: 0 }} />
       <Switch
         checked={shakeLimit.enabled}
         checkedChildren="开启防抖"
@@ -140,6 +161,8 @@ export default () => {
     </Space>
   );
 
+  console.log(formDatas);
+
   return (
     <PageContainer>
       <Card>
@@ -148,27 +171,46 @@ export default () => {
           colon={false}
           layout={'vertical'}
           preserve={false}
+          className={'scene-save'}
           onValuesChange={(changeValue, allValues) => {
             if (allValues.trigger?.device?.selectorValues) {
               setRequestParams({ trigger: allValues.trigger });
             } else {
               setRequestParams(undefined);
             }
+            FormModel = { ...allValues };
+            setFormDatas({ ...allValues });
           }}
         >
-          <Form.Item name={'name'} label={'名称'}>
+          <Form.Item
+            name={'name'}
+            label={<TitleComponent data={'名称'} style={{ margin: 0 }} />}
+            rules={[
+              { required: true, message: '请输入名称' },
+              {
+                max: 64,
+                message: intl.formatMessage({
+                  id: 'pages.form.tip.max64',
+                  defaultMessage: '最多输入64个字符',
+                }),
+              },
+            ]}
+            required
+          >
             <Input placeholder={'请输入名称'} />
           </Form.Item>
-          <Form.Item label={'触发方式'}>
+          <Form.Item label={<TitleComponent data={'触发方式'} style={{ margin: 0 }} />} required>
             <Form.Item name={['trigger', 'type']}>
               <TriggerWay onSelect={setTriggerType} />
             </Form.Item>
             {triggerType === TriggerWayType.timing && (
               <Form.Item name={['trigger', 'timer']}>
-                <TimingTrigger />
+                <TimingTrigger className={'trigger-type-content'} />
               </Form.Item>
             )}
-            {triggerType === TriggerWayType.device && <TriggerDevice form={form} />}
+            {triggerType === TriggerWayType.device && (
+              <TriggerDevice className={'trigger-type-content'} form={form} />
+            )}
           </Form.Item>
           {triggerType === TriggerWayType.device &&
           requestParams &&
@@ -186,10 +228,16 @@ export default () => {
           </Form.Item>
           <Form.Item
             label={
-              <>
-                <span>
-                  执行动作<span style={{ color: 'red', margin: '0 4px' }}>*</span>
-                </span>
+              <Space>
+                <TitleComponent
+                  data={
+                    <>
+                      <span>执行动作</span>
+                      <span style={{ color: 'red', margin: '0 4px' }}>*</span>
+                    </>
+                  }
+                  style={{ margin: 0 }}
+                />
                 <Tooltip
                   title={
                     <div>
@@ -212,12 +260,12 @@ export default () => {
                     form.setFieldsValue({ parallel: e.target.value });
                   }}
                 ></Radio.Group>
-              </>
+              </Space>
             }
           >
             <Form.List name="actions">
               {(fields, { add, remove }) => (
-                <>
+                <div className={'scene-actions'}>
                   {fields.map(({ key, name, ...restField }) => (
                     <ActionItems
                       key={key}
@@ -226,28 +274,41 @@ export default () => {
                       name={name}
                       triggerType={triggerType}
                       onRemove={() => remove(name)}
+                      actionItemData={FormModel.actions && FormModel.actions[name]}
                     />
                   ))}
-                  <Form.Item>
+                  <Form.Item noStyle>
                     <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
                       新增
                     </Button>
                   </Form.Item>
-                </>
+                </div>
               )}
             </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
+            label={<TitleComponent data={'说明'} style={{ margin: 0 }} />}
+            name={'description'}
+          >
+            <Input.TextArea showCount maxLength={200} placeholder={'请输入说明'} rows={4} />
           </Form.Item>
+          {triggerType === TriggerWayType.device &&
+          requestParams &&
+          requestParams.trigger?.device?.productId ? (
+            <Form.Item hidden name={'shakeLimit'} initialValue={DefaultShakeLimit}>
+              <Input />
+            </Form.Item>
+          ) : null}
           <Form.Item hidden name={'id'}>
             <Input />
           </Form.Item>
         </Form>
-        <PermissionButton isPermission={getOtherPermission(['add', 'update'])} onClick={saveData}>
+        <PermissionButton
+          isPermission={getOtherPermission(['add', 'update'])}
+          onClick={saveData}
+          type={'primary'}
+          loading={loading}
+        >
           保存
         </PermissionButton>
         {/*<Button*/}

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

@@ -7,11 +7,15 @@ import { queryOrgTree, querySelector } from '@/pages/rule-engine/Scene/Save/trig
 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';
+import classNames from 'classnames';
+import { observer } from '@formily/reactive-react';
+import { FormModel } from '../index';
 
 interface TriggerProps {
   value?: string;
   onChange?: (type: string) => void;
   form?: FormInstance;
+  className?: string;
 }
 
 enum OperatorEnum {
@@ -24,7 +28,7 @@ enum OperatorEnum {
   'invokeFunction' = 'invokeFunction',
 }
 
-export default (props: TriggerProps) => {
+export default observer((props: TriggerProps) => {
   const [productList, setProductList] = useState<any[]>([]);
   const [productId, setProductId] = useState('');
   const [selector, setSelector] = useState('fixed');
@@ -40,13 +44,6 @@ export default (props: TriggerProps) => {
   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 && resp.status === 200) {
@@ -93,13 +90,58 @@ export default (props: TriggerProps) => {
     }
   };
 
+  const productIdChange = useCallback(
+    (id: string, metadata: any) => {
+      setProductId(id);
+      handleMetadata(metadata);
+      if (selector === 'org') {
+        getOrgTree();
+      }
+    },
+    [selector],
+  );
+
+  const getProducts = async () => {
+    const resp = await getProductList({ paging: false });
+    if (resp && resp.status === 200) {
+      setProductList(resp.result);
+      if (FormModel.trigger && FormModel.trigger.device) {
+        const productItem = resp.result.find(
+          (item: any) => item.id === FormModel.trigger!.device.productId,
+        );
+
+        if (productItem) {
+          productIdChange(FormModel.trigger!.device.productId, productItem.metadata);
+        }
+      }
+    }
+  };
+
   useEffect(() => {
     getProducts();
     getSelector();
   }, []);
 
+  useEffect(() => {
+    if (FormModel.trigger && FormModel.trigger.device) {
+      const _device = FormModel.trigger.device;
+
+      if (_device.selector) {
+        if (_device.selector === 'org') {
+          getOrgTree();
+        }
+        setSelector(_device.selector);
+      }
+
+      console.log(_device.operation && _device.operation.operator);
+      if (_device.operation && _device.operation.operator) {
+        setOperation(_device.operation.operator);
+      }
+    }
+  }, [FormModel.trigger]);
+
   return (
-    <div>
+    <div className={classNames(props.className)}>
       <Row>
         <Col span={24}>
           <Space>
@@ -115,11 +157,7 @@ export default (props: TriggerProps) => {
                   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();
-                  }
+                  productIdChange(key, node.metadata);
                 }}
                 fieldNames={{ label: 'name', value: 'id' }}
               />
@@ -128,12 +166,6 @@ export default (props: TriggerProps) => {
               <Select
                 options={selectorOptions}
                 fieldNames={{ label: 'name', value: 'id' }}
-                onSelect={(key: string) => {
-                  if (key === 'org') {
-                    getOrgTree();
-                  }
-                  setSelector(key);
-                }}
                 style={{ width: 120 }}
               />
             </Form.Item>
@@ -158,7 +190,6 @@ export default (props: TriggerProps) => {
                   placeholder={'请选择触发类型'}
                   options={operatorOptions}
                   style={{ width: 140 }}
-                  onSelect={setOperation}
                 />
               </Form.Item>
             ) : null}
@@ -263,4 +294,4 @@ export default (props: TriggerProps) => {
       )}
     </div>
   );
-};
+});

+ 33 - 17
src/pages/rule-engine/Scene/Save/trigger/operation.tsx

@@ -1,5 +1,5 @@
 import { Col, Row, Select } from 'antd';
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
 import FunctionCall from '@/pages/rule-engine/Scene/Save/action/device/functionCall';
 
 interface OperatorProps {
@@ -10,7 +10,7 @@ interface OperatorProps {
 
 export default (props: OperatorProps) => {
   const [data, setData] = useState<any>({});
-  const [key, setKey] = useState('');
+  const [key, setKey] = useState<string | undefined>(undefined);
   const [propertiesItem, setPropertiesItem] = useState<any[]>([]);
 
   const objToArray = (_data: any) => {
@@ -19,39 +19,55 @@ export default (props: OperatorProps) => {
     });
   };
 
+  const findProperties = useCallback(
+    (_key: string, value: any) => {
+      if (props.propertiesList) {
+        const proItem = props.propertiesList.find((item: any) => item.id === _key);
+        if (proItem) {
+          return [
+            {
+              id: proItem.id,
+              name: proItem.name,
+              type: proItem.valueType ? proItem.valueType.type : '-',
+              format: proItem.valueType ? proItem.valueType.format : undefined,
+              options: proItem.valueType ? proItem.valueType.elements : undefined,
+              value: value,
+            },
+          ];
+        }
+        return [];
+      }
+      return [];
+    },
+    [props.propertiesList],
+  );
+
   useEffect(() => {
-    if (props.value) {
+    if (props.value && props.propertiesList?.length) {
       const _key = Object.keys(props.value)[0];
       setKey(_key);
-      setData(objToArray(props.value[_key]));
+      setData(objToArray(props.value));
+      setPropertiesItem(findProperties(_key, props.value[_key]));
     } else {
       setData({});
       setKey('');
     }
-  }, [props.value]);
+  }, [props.value, props.propertiesList]);
 
   return (
     <Row>
       <Col span={24}>
         <Select
           options={props.propertiesList || []}
+          value={key}
           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,
-              },
-            ]);
+          onSelect={(id: any) => {
+            // TODO 多选
             if (props.onChange) {
               props.onChange({ [id]: {} });
             }
@@ -64,7 +80,7 @@ export default (props: OperatorProps) => {
       {key && (
         <Col span={24}>
           <FunctionCall
-            value={[{ id: key, value: data[key] }]}
+            value={data}
             functionData={propertiesItem}
             onChange={(value) => {
               if (props.onChange) {

+ 0 - 360
src/pages/rule-engine/Scene/Save2/index.tsx

@@ -1,360 +0,0 @@
-import {
-  ArrayItems,
-  DatePicker,
-  Editable,
-  FormButtonGroup,
-  FormGrid,
-  FormItem,
-  FormLayout,
-  Input,
-  NumberPicker,
-  Radio,
-  Select,
-  Space,
-  Submit,
-} from '@formily/antd';
-import type { Field } from '@formily/core';
-import { createForm, FieldDataSource, onFieldReact, onFieldValueChange } from '@formily/core';
-import { createSchemaField, FormProvider } from '@formily/react';
-import { action } from '@formily/reactive';
-import {
-  queryMessageConfig,
-  queryMessageTemplate,
-  queryMessageType,
-  queryProductList,
-} from '@/pages/rule-engine/Scene/Save/action/service';
-import { Card } from 'antd';
-import { useMemo } from 'react';
-import type { ISchema } from '@formily/json-schema';
-
-export default () => {
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Editable,
-      DatePicker,
-      Space,
-      Radio,
-      Input,
-      Select,
-      ArrayItems,
-      FormLayout,
-      FormGrid,
-      NumberPicker,
-    },
-  });
-
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      layout: {
-        type: 'void',
-        'x-component': 'FormLayout',
-        'x-component-props': {
-          layout: 'vertical',
-        },
-        properties: {
-          actions: {
-            type: 'array',
-            'x-component': 'ArrayItems',
-            'x-decorator': 'FormItem',
-            title: '执行动作',
-            items: {
-              type: 'object',
-              title: '执行动作',
-              properties: {
-                space: {
-                  type: 'void',
-                  'x-component': 'Space',
-                  properties: {
-                    executor: {
-                      type: 'string',
-                      'x-decorator': 'FormItem',
-                      'x-component': 'Select',
-                      'x-component-props': {
-                        style: {
-                          width: 160,
-                        },
-                      },
-                      enum: [
-                        { label: '消息通知', value: 'message' },
-                        { label: '设备输出', value: 'device' },
-                        { label: '延迟执行', value: 'delay' },
-                      ],
-                    },
-                    notify: {
-                      type: 'object',
-                      'x-decorator': 'FormItem',
-                      'x-reactions': [
-                        {
-                          dependencies: ['.executor'],
-                          fulfill: {
-                            state: {
-                              visible: "{{$deps[0] === 'message'}}",
-                            },
-                          },
-                        },
-                      ],
-                      properties: {
-                        grid: {
-                          type: 'void',
-                          'x-component': 'FormGrid',
-                          'x-component-props': {
-                            minColumns: [4, 6, 10],
-                          },
-                          properties: {
-                            messageType: {
-                              type: 'string',
-                              'x-decorator': 'FormItem',
-                              'x-component': 'Select',
-                              'x-component-props': {
-                                style: { width: 160 },
-                                fieldNames: { label: 'name', value: 'id' },
-                              },
-                              'x-reactions': ['{{useAsyncDataSource(getMessageType)}}'],
-                            },
-                            notifierId: {
-                              type: 'string',
-                              'x-decorator': 'FormItem',
-                              'x-component': 'Select',
-                              'x-component-props': {
-                                style: { width: 160 },
-                                fieldNames: { label: 'name', value: 'id' },
-                              },
-                            },
-                            templateId: {
-                              type: 'string',
-                              'x-decorator': 'FormItem',
-                              'x-component': 'Select',
-                              'x-component-props': {
-                                style: { width: 160 },
-                              },
-                            },
-                          },
-                        },
-                      },
-                    },
-                    variables: {
-                      type: 'object',
-                      'x-decorator': 'FormItem',
-                      properties: {},
-                      'x-reactions': [
-                        {
-                          dependencies: ['.executor'],
-                          fulfill: {
-                            state: {
-                              visible: "{{$deps[0] === 'message'}}",
-                            },
-                          },
-                        },
-                      ],
-                    },
-                    device: {
-                      type: 'object',
-                      'x-decorator': 'FormItem',
-                      'x-component': 'Space',
-                      'x-reactions': [
-                        {
-                          dependencies: ['.executor'],
-                          fulfill: {
-                            state: {
-                              visible: "{{$deps[0] === 'device'}}",
-                            },
-                          },
-                        },
-                      ],
-                      properties: {
-                        productId: {
-                          type: 'string',
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Select',
-                          'x-component-props': {
-                            style: { width: 200 },
-                            fieldNames: { label: 'name', value: 'id' },
-                          },
-                          'x-reactions': ['{{useAsyncDataSource(getProductList)}}'],
-                        },
-                        selector: {
-                          type: 'string',
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Select',
-                          'x-component-props': {
-                            style: { width: 200 },
-                          },
-                          enum: [
-                            { label: '固定设备', value: 'device' },
-                            { label: '按标签', value: 'tag' },
-                            { label: '按关系', value: 'relation' },
-                          ],
-                        },
-                        'message.messageType': {
-                          type: 'string',
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Select',
-                          'x-component-props': {
-                            style: { width: 160 },
-                          },
-                          enum: [
-                            { label: '功能调用', value: 'INVOKE_FUNCTION' },
-                            { label: '读取属性', value: 'READ_PROPERTY' },
-                            { label: '设置属性', value: 'WRITE_PROPERTY' },
-                          ],
-                        },
-                        value: {
-                          type: 'object',
-                          'x-decorator': 'FormItem',
-                        },
-                      },
-                    },
-                    delay: {
-                      type: 'object',
-                      'x-decorator': 'FormItem',
-                      'x-reactions': [
-                        {
-                          dependencies: ['.executor'],
-                          fulfill: {
-                            state: {
-                              visible: "{{$deps[0] === 'delay'}}",
-                            },
-                          },
-                        },
-                      ],
-                      properties: {
-                        time: {
-                          type: 'number',
-                          'x-decorator': 'FormItem',
-                          'x-component': 'NumberPicker',
-                          'x-component-props': {
-                            style: {
-                              width: 240,
-                            },
-                          },
-                        },
-                        unit: {
-                          type: 'string',
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Select',
-                          'x-component-props': {
-                            style: {
-                              width: 160,
-                            },
-                          },
-                          enum: [
-                            { label: '秒', value: 'seconds' },
-                            { label: '分', value: 'minutes' },
-                            { label: '小时', value: 'hours' },
-                          ],
-                        },
-                      },
-                    },
-                  },
-                },
-                remove: {
-                  type: 'void',
-                  'x-decorator': 'FormItem',
-                  'x-component': 'ArrayItems.Remove',
-                },
-              },
-            },
-            properties: {
-              add: {
-                type: 'void',
-                title: '添加条目',
-                'x-component': 'ArrayItems.Addition',
-              },
-            },
-          },
-        },
-      },
-    },
-  };
-
-  const form = useMemo(
-    () =>
-      createForm({
-        effects: () => {
-          onFieldReact('actions.*.notify.notifierId', async (field, f) => {
-            const key = field.query('.messageType').get('value');
-            f.clearFormGraph('.variables');
-            (field as Field).value = undefined;
-            if (key) {
-              (field as Field).loading = true;
-              const resp = await queryMessageConfig({ terms: [{ column: 'type$IN', value: key }] });
-              (field as Field).loading = false;
-              if (resp.status === 200) {
-                (field as Field).dataSource = resp.result;
-              }
-            }
-          });
-          onFieldReact('actions.*.notify.templateId', async (field) => {
-            const key = field.query('.notifierId').get('value');
-            (field as Field).value = undefined;
-            if (key) {
-              (field as Field).loading = true;
-              const resp = await queryMessageTemplate({
-                terms: [{ column: 'configId', value: key }],
-              });
-              (field as Field).loading = false;
-              if (resp.status === 200) {
-                (field as Field).dataSource = resp.result.map((item: any) => ({
-                  label: item.name,
-                  value: item.id,
-                  data: item,
-                }));
-              }
-            }
-          });
-          onFieldValueChange('actions.*.notify.templateId', async (field) => {
-            console.log(field);
-
-            const templateData = field.dataSource.find((item) => item.value === field.value);
-            if (templateData) {
-              const data = templateData.data;
-              if (data.variableDefinitions) {
-                const obj = {};
-                data.variableDefinitions.forEach((item: any) => {
-                  obj[item.id] = {
-                    title: item.name,
-                    type: 'string',
-                    'x-decorator': 'FormItem',
-                    'x-component': 'Input',
-                  };
-                });
-              }
-            }
-          });
-        },
-      }),
-    [],
-  );
-
-  const getMessageType = async () => await queryMessageType();
-
-  const getProductList = async () =>
-    await queryProductList({ sorts: [{ name: 'createTime', order: 'desc' }] });
-
-  const useAsyncDataSource =
-    (services: (arg0: Field) => Promise<FieldDataSource>) => (field: Field) => {
-      field.loading = true;
-      services(field).then(
-        action.bound!((resp: any) => {
-          field.dataSource = resp.result;
-          field.loading = false;
-        }),
-      );
-    };
-
-  return (
-    <Card>
-      <FormProvider form={form}>
-        <SchemaField
-          schema={schema}
-          scope={{ useAsyncDataSource, getMessageType, getProductList }}
-        />
-        <FormButtonGroup>
-          <Submit onSubmit={console.log}>提交</Submit>
-        </FormButtonGroup>
-      </FormProvider>
-    </Card>
-  );
-};

+ 4 - 0
src/pages/rule-engine/Scene/index.tsx

@@ -45,6 +45,10 @@ const Scene = () => {
               }
             : undefined
         }
+        onClick={() => {
+          const url = getMenuPathByCode('rule-engine/Scene/Save');
+          history.push(`${url}?id=${record.id}`);
+        }}
       >
         <EditOutlined />
         {type !== 'table' &&

+ 7 - 0
src/pages/rule-engine/Scene/service.ts

@@ -9,6 +9,13 @@ class Service extends BaseService<SceneItem> {
 
   stopScene = (id: string) => request(`${this.uri}/${id}/_disable`, { method: 'PUT' });
 
+  updateScene = (data: any) => request(`${this.uri}/${data.id}`, { method: 'PUT', data });
+
+  // getParseTerm = (data: Record<string, any>) =>
+  //   request(`${this.uri}/parse-term-column`, {
+  //     method: 'POST',
+  //     data,
+  //   }).then((resp) => resp.result);
   // getParseTerm = (data: Record<string, any>) => {
   //   const oldParams = Store.get('request-params-parse-term');
   //   const list = Store.get('parse-term');

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

@@ -19,3 +19,39 @@ interface SceneItem {
   name: string;
   describe: string;
 }
+
+type TriggerType = {
+  type?: string;
+  shakeLimit?: any;
+  device?: any;
+  timer?: any;
+};
+
+type TermsType = {
+  column?: string;
+  value?: any;
+  type?: string;
+  termType?: string;
+  options?: any[];
+  terms?: any[];
+};
+
+type ActionsType = {
+  executor?: string;
+  notify?: any;
+  delay?: {
+    time?: number;
+    unit?: string;
+  };
+  device?: any;
+};
+
+type FormModelType = {
+  id?: string;
+  name?: string;
+  trigger?: TriggerType;
+  terms?: TermsType;
+  parallel?: boolean;
+  actions?: ActionsType[];
+  description?: string;
+};