wzyyy 3 лет назад
Родитель
Сommit
765f9579b2
38 измененных файлов с 1001 добавлено и 390 удалено
  1. 19 1
      src/components/FMonacoEditor/index.tsx
  2. 8 0
      src/global.less
  3. 3 2
      src/hooks/route/useHistory.tsx
  4. 1 1
      src/locales/zh-CN/pages.ts
  5. 1 1
      src/pages/device/Instance/Detail/Config/index.tsx
  6. 4 1
      src/pages/device/Instance/Detail/Diagnose/index.tsx
  7. 3 0
      src/pages/device/Instance/Detail/Info/index.tsx
  8. 9 2
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx
  9. 29 17
      src/pages/device/Instance/Detail/index.tsx
  10. 1 0
      src/pages/device/Instance/typings.d.ts
  11. 32 15
      src/pages/device/Product/Detail/Access/AccessConfig/index.tsx
  12. 8 8
      src/pages/device/Product/Detail/Access/index.less
  13. 137 101
      src/pages/device/Product/Detail/Access/index.tsx
  14. 1 1
      src/pages/device/Product/Detail/index.tsx
  15. 2 0
      src/pages/device/Product/index.tsx
  16. 5 0
      src/pages/device/Product/service.ts
  17. 1 0
      src/pages/device/Product/typings.d.ts
  18. 1 1
      src/pages/device/components/Metadata/Base/index.tsx
  19. 20 2
      src/pages/device/components/Metadata/Import/index.tsx
  20. 5 4
      src/pages/home/components/Steps.tsx
  21. 4 4
      src/pages/home/comprehensive/index.tsx
  22. 41 0
      src/pages/home/device/index.tsx
  23. 94 0
      src/pages/link/AccessConfig/Detail/Media/SipSelectComponent/index.tsx
  24. 25 3
      src/pages/link/AccessConfig/Detail/Media/index.tsx
  25. 4 0
      src/pages/link/AccessConfig/service.ts
  26. 22 22
      src/pages/link/Protocol/save/index.tsx
  27. 6 0
      src/pages/link/Type/Detail/index.less
  28. 237 66
      src/pages/link/Type/Detail/index.tsx
  29. 43 33
      src/pages/rule-engine/Scene/Save/Explanation.tsx
  30. 31 34
      src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx
  31. 42 10
      src/pages/rule-engine/Scene/Save/action/action.tsx
  32. 45 5
      src/pages/rule-engine/Scene/Save/action/device/ConditionalFiltering.tsx
  33. 63 38
      src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx
  34. 11 0
      src/pages/rule-engine/Scene/Save/action/device/functionCall.tsx
  35. 17 7
      src/pages/rule-engine/Scene/Save/action/device/index.tsx
  36. 20 10
      src/pages/rule-engine/Scene/Save/index.tsx
  37. 1 1
      src/pages/system/DataSource/Management/EditTable.tsx
  38. 5 0
      yarn.lock

+ 19 - 1
src/components/FMonacoEditor/index.tsx

@@ -1,5 +1,23 @@
 import MonacoEditor from 'react-monaco-editor';
 import { connect, mapProps } from '@formily/react';
+import { useState } from 'react';
 
-const FMonacoEditor = connect(MonacoEditor, mapProps());
+const JMonacoEditor = (props: any) => {
+  const [loading, setLoading] = useState(false);
+
+  return (
+    <div
+      ref={() => {
+        setTimeout(() => {
+          setLoading(true);
+        }, 100);
+      }}
+      style={{ height: '100%', width: '100%' }}
+    >
+      {loading && <MonacoEditor {...props} />}
+    </div>
+  );
+};
+
+const FMonacoEditor = connect(JMonacoEditor, mapProps());
 export default FMonacoEditor;

+ 8 - 0
src/global.less

@@ -131,3 +131,11 @@ ol {
   text-align: left;
   text-overflow: ellipsis;
 }
+
+// fuck chrome
+input[type='text'],
+input[type='password'],
+input[type='number'],
+input[type='tel'] {
+  box-shadow: inset 0 0 0 100vw white !important;
+}

+ 3 - 2
src/hooks/route/useHistory.tsx

@@ -1,9 +1,9 @@
 import { useHistory } from 'umi';
 import { useEffect, useState } from 'react';
 import type { LocationState, Path } from 'history';
-import { model } from '@formily/reactive';
+import { observable } from '@formily/reactive';
 
-export const historyStateModel = model<{ state: any }>({ state: {} });
+export const historyStateModel = observable<{ state: any }>({ state: {} });
 
 const useHistories = () => {
   const umiHistory = useHistory();
@@ -14,6 +14,7 @@ const useHistories = () => {
     if (state) {
       historyStateModel.state[location] = state;
     }
+
     umiHistory.push(location, state);
   };
 

+ 1 - 1
src/locales/zh-CN/pages.ts

@@ -37,7 +37,7 @@ export default {
   'pages.data.option.invoke': '执行',
   'pages.data.option.cancel': '取消',
   'pages.data.option.view': '查看',
-  'pages.searchTable.new': '新',
+  'pages.searchTable.new': '新',
   'pages.searchTable.titleStatus': '状态',
   'pages.searchTable.titleStatus.all': '全部',
   'pages.searchTable.titleStatus.normal': '正常',

+ 1 - 1
src/pages/device/Instance/Detail/Config/index.tsx

@@ -22,7 +22,7 @@ const Config = () => {
     }
   }, []);
 
-  const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
+  const [metadata, setMetadata] = useState<ConfigMetadata[]>(InstanceModel.config);
   const [visible, setVisible] = useState<boolean>(false);
   const { permission } = PermissionButton.usePermission('device/Instance');
 

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

@@ -47,8 +47,11 @@ const Diagnose = observer(() => {
     return () => {
       DiagnoseStatusModel.list = [];
       DiagnoseStatusModel.count = 0;
+      DiagnoseStatusModel.percent = 0;
+      DiagnoseStatusModel.status = 'loading';
+      DiagnoseStatusModel.state = 'loading';
     };
-  }, []);
+  }, [InstanceModel.active]);
 
   const activeStyle = {
     background: '#FFFFFF',

+ 3 - 0
src/pages/device/Instance/Detail/Info/index.tsx

@@ -65,6 +65,9 @@ const Info = observer(() => {
               </div>
             </Tooltip>
           </Descriptions.Item>
+          <Descriptions.Item label={'产品分类'}>
+            {InstanceModel.detail?.classifiedName}
+          </Descriptions.Item>
           <Descriptions.Item
             label={intl.formatMessage({
               id: 'pages.device.instanceDetail.deviceType',

+ 9 - 2
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -139,11 +139,18 @@ const EditableTable = (props: Props) => {
       render: (text: any, record: any) => <span>{record.valueType?.type}</span>,
     },
     {
-      title: '映射状态',
+      title: (
+        <span>
+          映射状态
+          <Tooltip title="默认映射:当前属性不做映射,使用物模型属性进行数据处理。">
+            <QuestionCircleOutlined />
+          </Tooltip>
+        </span>
+      ),
       dataIndex: 'customMapping',
       render: (text: any) => (
         <span>
-          <Badge status={text ? 'success' : 'error'} text={text ? '已映射' : '未映射'} />
+          <Badge status={'success'} text={text ? '已映射' : '默认映射'} />
         </span>
       ),
     },

+ 29 - 17
src/pages/device/Instance/Detail/index.tsx

@@ -1,7 +1,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { InstanceModel } from '@/pages/device/Instance';
 import { history, useParams } from 'umi';
-import { Badge, Card, Descriptions, Divider, Space, Tooltip } from 'antd';
+import { Badge, Card, Descriptions, Divider, message, Space, Tooltip } from 'antd';
 import type { ReactNode } from 'react';
 import { useEffect, useState } from 'react';
 import { observer } from '@formily/react';
@@ -24,7 +24,7 @@ import SystemConst from '@/utils/const';
 import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { PermissionButton } from '@/components';
-import {  QuestionCircleOutlined } from '@ant-design/icons';
+import { ExclamationCircleOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
 import Service from '@/pages/device/Instance/service';
 import useLocation from '@/hooks/route/useLocation';
 import { onlyMessage } from '@/utils/util';
@@ -408,21 +408,33 @@ const InstanceDetail = observer(() => {
           </Space>
         </div>
       }
-    // extra={[
-    //   statusMap[0],
-    //   <Button key="2">
-    //     {intl.formatMessage({
-    //       id: 'pages.device.productDetail.disable',
-    //       defaultMessage: '停用',
-    //     })}
-    //   </Button>,
-    //   <Button key="1" type="primary">
-    //     {intl.formatMessage({
-    //       id: 'pages.device.productDetail.setting',
-    //       defaultMessage: '应用配置',
-    //     })}
-    //   </Button>,
-    // ]}
+      extra={[
+        // <Button key="2">
+        //   {intl.formatMessage({
+        //     id: 'pages.device.productDetail.disable',
+        //     defaultMessage: '停用',
+        //   })}
+        // </Button>,
+        // <Button key="1" type="primary">
+        //   {intl.formatMessage({
+        //     id: 'pages.device.productDetail.setting',
+        //     defaultMessage: '应用配置',
+        //   })}
+        // </Button>,
+        <SyncOutlined
+          onClick={() => {
+            getDetail(params.id);
+            service.getConfigMetadata(params.id).then((config) => {
+              if (config.status === 200) {
+                InstanceModel.config = config?.result || [];
+                message.success('操作成功!');
+              }
+            });
+          }}
+          style={{ fontSize: 20, marginRight: 20 }}
+          key="1"
+        />,
+      ]}
     >
       {list.find((k) => k.key === InstanceModel.active)?.component}
     </PageContainer>

+ 1 - 0
src/pages/device/Instance/typings.d.ts

@@ -44,6 +44,7 @@ export type DeviceInstance = {
   accessId?: string;
   features?: any[];
   parentId?: string;
+  classifiedName?: string;
 };
 
 type Unit = {

+ 32 - 15
src/pages/device/Product/Detail/Access/AccessConfig/index.tsx

@@ -12,6 +12,8 @@ import { getMenuPathByCode } from '@/utils/menu';
 import PermissionButton from '@/components/PermissionButton';
 import { onlyMessage } from '@/utils/util';
 import Empty from '@/components/Empty';
+import _ from 'lodash';
+import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
 
 interface Props {
   close: () => void;
@@ -128,6 +130,15 @@ const AccessConfig = (props: Props) => {
     handleSearch(param);
   }, []);
 
+  const modifyArray = (oldData: any[], newData: any[]) => {
+    newData.map((item) => {
+      if (!_.map(oldData, 'id').includes(item.id)) {
+        oldData.push(item);
+      }
+    });
+    return oldData;
+  };
+
   return (
     <Modal
       onCancel={() => {
@@ -138,7 +149,7 @@ const AccessConfig = (props: Props) => {
       title={'设备接入配置'}
       onOk={async () => {
         if (!!currrent) {
-          const resp: any = await service1.update({
+          const obj: any = {
             ...productModel.current,
             transportProtocol: currrent.transport,
             protocolName: currrent.protocolDetail.name,
@@ -146,7 +157,26 @@ const AccessConfig = (props: Props) => {
             accessName: currrent.name,
             accessProvider: currrent.provider,
             messageProtocol: currrent.protocol,
-          });
+          };
+          const metatdata = JSON.parse(productModel.current?.metadata || '{}');
+          if (obj.accessProvider === 'gb28181-2016') {
+            const response = await service.getConfigView(
+              obj?.messageProtocol || '',
+              obj?.transportProtocol || '',
+            );
+            if (response.status === 200) {
+              const ndata = JSON.parse(response.result?.metadata || '{}');
+              const mdata = {
+                events: modifyArray(metatdata?.events || [], ndata?.events || []),
+                properties: modifyArray(metatdata?.properties || [], ndata?.properties || []),
+                functions: modifyArray(metatdata?.functions || [], ndata?.functions || []),
+                tags: modifyArray(metatdata?.tags || [], ndata?.tags || []),
+              };
+              MetadataAction.insert(mdata);
+              obj.metadata = JSON.stringify(mdata);
+            }
+          }
+          const resp: any = await service1.update(obj);
           if (resp.status === 200) {
             service1.detail(productModel.current?.id || '').then((res) => {
               if (res.status === 200) {
@@ -155,19 +185,6 @@ const AccessConfig = (props: Props) => {
               }
               close();
             });
-            // service1
-            //   .changeDeploy(productModel.current?.id || '', 'deploy')
-            //   .subscribe((response) => {
-            //     if (response) {
-            //       service1.detail(productModel.current?.id || '').then((res) => {
-            //         if (res.status === 200) {
-            //           productModel.current = { ...res.result };
-            //           message.success('操作成功!');
-            //         }
-            //         close();
-            //       });
-            //     }
-            //   });
           }
         } else {
           onlyMessage('请选择接入方式', 'error');

+ 8 - 8
src/pages/device/Product/Detail/Access/index.less

@@ -33,11 +33,11 @@
 
 .driver {
   .driver-next-btn {
-    background-color: #2F54EB !important;
     color: #fff !important;
-    text-shadow: 0 0 black !important;
     font-size: 14px !important;
     line-height: 22px !important;
+    text-shadow: 0 0 black !important;
+    background-color: #2f54eb !important;
   }
 
   .driver-prev-btn {
@@ -45,16 +45,16 @@
     line-height: 22px !important;
     background-color: #fff !important;
   }
-  .driver-prev-btn.driver-disabled{
+  .driver-prev-btn.driver-disabled {
     display: none !important;
   }
 
   .driver-close-btn {
-    background-color: #fff !important;
-    border: none !important;
+    padding: 5px 0 0 0 !important;
     color: #828282 !important;
     font-size: 14px !important;
-    padding: 5px 0 0 0 !important;
+    background-color: #fff !important;
+    border: none !important;
   }
 
   .driver-popover-description {
@@ -67,7 +67,7 @@
 
     #guide {
       margin-top: 3px;
-      font-size: 14px
+      font-size: 14px;
     }
   }
-}
+}

+ 137 - 101
src/pages/device/Product/Detail/Access/index.tsx

@@ -40,7 +40,7 @@ const Access = () => {
   const [access, setAccess] = useState<any>();
   const { permission } = usePermissions('device/Product');
   const [dataSource, setDataSource] = useState<any[]>([]);
-
+  const [storageList, setStorageList] = useState<any[]>([]);
 
   const MetworkTypeMapping = new Map();
   MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
@@ -66,8 +66,7 @@ const Access = () => {
         title: `<div id='title'>配置物模型</div><div id='guide'>1/3</div>`,
         description: `配置产品物模型,实现设备在云端的功能描述。`,
         position: 'bottom',
-
-      }
+      },
     },
     {
       element: '.ant-switch',
@@ -75,8 +74,8 @@ const Access = () => {
         className: 'driver',
         title: `<div id='title'>启用产品</div><div id='guide'>2/3</div>`,
         description: '启用产品后,可在产品下新增设备。',
-        position: 'bottom'
-      }
+        position: 'bottom',
+      },
     },
     {
       element: '.ant-descriptions-item-label',
@@ -84,10 +83,10 @@ const Access = () => {
         className: 'driver',
         title: `<div id='title'>添加设备</div><div id='guide'>3/3</div>`,
         description: '添加设备,并连接到平台。',
-        position: 'bottom'
-      }
-    }
-  ]
+        position: 'bottom',
+      },
+    },
+  ];
   const steps1 = [
     {
       element: '.config',
@@ -96,8 +95,7 @@ const Access = () => {
         title: `<div id='title'>填写配置</div><div id='guide'>1/4</div>`,
         description: `填写设备接入所需的配置参数。`,
         position: 'right',
-
-      }
+      },
     },
     {
       element: '#metadata-driver',
@@ -106,8 +104,7 @@ const Access = () => {
         title: `<div id='title'>配置物模型</div><div id='guide'>2/4</div>`,
         description: `配置产品物模型,实现设备在云端的功能描述。`,
         position: 'bottom',
-
-      }
+      },
     },
     {
       element: '.ant-switch',
@@ -115,8 +112,8 @@ const Access = () => {
         className: 'driver',
         title: `<div id='title'>启用产品</div><div id='guide'>3/4</div>`,
         description: '启用产品后,可在产品下新增设备。',
-        position: 'bottom'
-      }
+        position: 'bottom',
+      },
     },
     {
       element: '.ant-descriptions-item-label',
@@ -124,10 +121,10 @@ const Access = () => {
         className: 'driver',
         title: `<div id='title'>添加设备</div><div id='guide'>4/4</div>`,
         description: '添加设备,并连接到平台。',
-        position: 'bottom'
-      }
-    }
-  ]
+        position: 'bottom',
+      },
+    },
+  ];
 
   const queryAccessDetail = (id: string) => {
     service
@@ -367,12 +364,25 @@ const Access = () => {
           });
       }
     }
+    productService.getStoragList().then((resp) => {
+      if (resp.status === 200) {
+        setStorageList(
+          resp.result.map((i: any) => ({
+            label: i.name,
+            value: i.id,
+          })),
+        );
+      }
+    });
   }, [productModel.current]);
 
 
   const form = createForm({
     validateFirst: true,
-    initialValues: productModel.current?.configuration,
+    initialValues: {
+      ...productModel.current?.configuration,
+      storePolicy: productModel.current?.storePolicy,
+    },
   });
 
   const SchemaField = createSchemaField({
@@ -387,13 +397,16 @@ const Access = () => {
   });
 
   const configToSchema = (data: ConfigProperty[]) => {
-    const obj = {};
+    const obj: any = {};
     data.forEach((item) => {
       obj[item?.property] = {
         type: 'string',
         title: item.name,
         'x-decorator': 'FormItem',
         'x-component': componentMap[item.type.type],
+        'x-component-props': {
+          placeholder: item.type.type === 'enum' ? '请选择' : '请输入',
+        },
         'x-decorator-props': {
           tooltip: item.description,
           gridSpan: 1,
@@ -403,14 +416,15 @@ const Access = () => {
         enum:
           item?.type?.type === 'enum' && item?.type?.elements
             ? (item?.type?.elements || []).map((t: { value: string; text: string }) => {
-              return {
-                label: t.text,
-                value: t.value,
-              };
-            })
+                return {
+                  label: t.text,
+                  value: t.value,
+                };
+              })
             : [],
       };
     });
+
     return obj;
   };
 
@@ -426,81 +440,101 @@ const Access = () => {
   };
 
   const renderConfigCard = () => {
-    return (
-      metadata &&
-      metadata.length > 0 &&
-      metadata?.map((item: any) => {
-        const itemSchema: ISchema = {
-          type: 'object',
-          properties: {
-            grid: {
-              type: 'void',
-              'x-component': 'FormGrid',
-              'x-component-props': {
-                maxColumns: 1,
-                minColumns: 1,
-                columnGap: 48,
-              },
-              title: (
-                <TitleComponent
-                  data={
-                    <div className='config'>
-                      {item.name}
-                      <Tooltip title="此配置来自于该产品接入方式所选择的协议">
-                        <QuestionCircleOutlined />
-                      </Tooltip>
-                    </div>
-                  }
-                />
-              ),
-              'x-decorator': 'FormItem',
-              'x-decorator-props': {
-                gridSpan: 1,
-                labelAlign: 'left',
-                layout: 'vertical',
-              },
-              properties: configToSchema(item.properties),
+    const itemSchema: any = (metadata || []).map((item: any) => {
+      return {
+        type: 'object',
+        properties: {
+          grid: {
+            type: 'void',
+            'x-component': 'FormGrid',
+            'x-component-props': {
+              maxColumns: 1,
+              minColumns: 1,
+              columnGap: 48,
             },
+            title: (
+              <TitleComponent
+                data={
+                  <div className="config">
+                    {item.name}
+                    <Tooltip title="此配置来自于该产品接入方式所选择的协议">
+                      <QuestionCircleOutlined />
+                    </Tooltip>
+                  </div>
+                }
+              />
+            ),
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 1,
+              labelAlign: 'left',
+              layout: 'vertical',
+            },
+            properties: configToSchema(item.properties),
           },
-        };
-
-        return (
-          <PreviewText.Placeholder value="-" key={'config'}>
-            <Form form={form} layout="vertical">
-              <FormLayout>
-                <SchemaField schema={itemSchema} />
-              </FormLayout>
-              <Button
-                type="primary"
-                onClick={async () => {
-                  const values = (await form.submit()) as any;
-                  const resp = await productService.modify(id || '', {
-                    id,
-                    configuration: { ...values },
-                  });
-                  if (resp.status === 200) {
-                    onlyMessage('操作成功!');
-                    if ((window as any).onTabSaveSuccess) {
-                      if (resp.result) {
-                        (window as any).onTabSaveSuccess(resp);
-                        setTimeout(() => window.close(), 300);
-                      }
-                    } else {
-                      getDetailInfo();
-                    }
+        },
+      };
+    });
+    const schema: ISchema = {
+      type: 'object',
+      properties: {
+        ...itemSchema,
+        storePolicy: {
+          type: 'string',
+          title: <TitleComponent data={'存储策略'} />,
+          'x-decorator': 'FormItem',
+          'x-component': 'Select',
+          'x-component-props': {
+            placeholder: '请选择存储策略',
+          },
+          // required: true,
+          default: 'default-column',
+          'x-decorator-props': {
+            // tooltip: '使用指定的存储策略来存储设备数据',
+            gridSpan: 1,
+            labelAlign: 'left',
+            layout: 'vertical',
+          },
+          enum: storageList,
+        },
+      },
+    };
+    return (
+      <PreviewText.Placeholder value="-" key={'config'}>
+        <Form form={form} layout="vertical">
+          <FormLayout>
+            <SchemaField schema={schema} />
+          </FormLayout>
+          <Button
+            type="primary"
+            onClick={async () => {
+              const values = (await form.submit()) as any;
+              const { storePolicy, ...extra } = values;
+              const resp = await productService.modify(id || '', {
+                id,
+                configuration: { ...extra },
+                storePolicy: storePolicy,
+              });
+              if (resp.status === 200) {
+                onlyMessage('操作成功!');
+                if ((window as any).onTabSaveSuccess) {
+                  if (resp.result) {
+                    (window as any).onTabSaveSuccess(resp);
+                    setTimeout(() => window.close(), 300);
                   }
-                }}
-              >
-                保存
-              </Button>
-            </Form>
-          </PreviewText.Placeholder>
-        );
-      })
+                } else {
+                  getDetailInfo();
+                }
+              }
+            }}
+          >
+            保存
+          </Button>
+        </Form>
+      </PreviewText.Placeholder>
     );
   };
 
-
   return (
     <div>
       {!visible ? (
@@ -597,14 +631,16 @@ const Access = () => {
                 <TitleComponent data={'连接信息'} />
                 {(access?.channelInfo?.addresses || []).length > 0
                   ? (access?.channelInfo?.addresses || []).map((item: any) => (
-                    <div key={item.address}>
-                      <Badge color={item.health === -1 ? 'red' : 'green'} text={item.address} />
-                    </div>
-                  ))
+                      <div key={item.address}>
+                        <Badge color={item.health === -1 ? 'red' : 'green'} text={item.address} />
+                      </div>
+                    ))
                   : '暂无连接信息'}
               </div>
 
-              <div className={styles.item} id='driver-config'>{renderConfigCard()}</div>
+              <div className={styles.item} id="driver-config">
+                {renderConfigCard()}
+              </div>
             </div>
           </Col>
           {config?.routes && config?.routes?.length > 0 && (
@@ -613,7 +649,7 @@ const Access = () => {
                 <div>
                   <div style={{ fontWeight: '600', marginBottom: 10 }}>
                     {access?.provider === 'mqtt-server-gateway' ||
-                      access?.provider === 'mqtt-client-gateway'
+                    access?.provider === 'mqtt-client-gateway'
                       ? 'topic'
                       : 'URL信息'}
                   </div>

+ 1 - 1
src/pages/device/Product/Detail/index.tsx

@@ -54,7 +54,7 @@ const ProductDetail = observer(() => {
     {
       key: 'metadata',
       tab: (
-        <div id='metadata-driver'>
+        <div id="metadata-driver">
           {intl.formatMessage({
             id: 'pages.device.instanceDetail.metadata',
             defaultMessage: '物模型',

+ 2 - 0
src/pages/device/Product/index.tsx

@@ -84,6 +84,8 @@ const Product = observer(() => {
 
   useEffect(() => {
     const { state } = location;
+    console.log(state);
+
     if (state && state.save) {
       setCurrent(undefined);
       setVisible(true);

+ 5 - 0
src/pages/device/Product/service.ts

@@ -164,6 +164,11 @@ class Service extends BaseService<ProductItem> {
     request(`/${SystemConst.API_BASE}/protocol/${id}/detail`, {
       method: 'POST',
     });
+  // 存储策略
+  public getStoragList = () =>
+    request(`/${SystemConst.API_BASE}/device/product/storage/policies`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

+ 1 - 0
src/pages/device/Product/typings.d.ts

@@ -27,6 +27,7 @@ export type ProductItem = {
   accessId?: string;
   accessName?: string;
   photoUrl?: string;
+  storePolicy?: string;
   accessProvider?: string;
 };
 

+ 1 - 1
src/pages/device/components/Metadata/Base/index.tsx

@@ -208,7 +208,7 @@ const BaseMetadata = observer((props: Props) => {
           >
             {intl.formatMessage({
               id: 'pages.searchTable.new',
-              defaultMessage: '新',
+              defaultMessage: '新',
             })}
           </PermissionButton>,
         ]}

+ 20 - 2
src/pages/device/components/Metadata/Import/index.tsx

@@ -12,6 +12,9 @@ import { Store } from 'jetlinks-store';
 import SystemConst from '@/utils/const';
 import { onlyMessage } from '@/utils/util';
 import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { InstanceModel } from '@/pages/device/Instance';
+import _ from 'lodash';
+import { DeviceMetadata } from '@/pages/device/Product/typings';
 
 interface Props {
   visible: boolean;
@@ -181,6 +184,19 @@ const Import = (props: Props) => {
     },
   };
 
+  const operateLimits = (mdata: DeviceMetadata) => {
+    const obj: DeviceMetadata = { ...mdata };
+    const old = JSON.parse(InstanceModel.detail?.metadata || '{}');
+    const fid = _.map(InstanceModel.detail?.features || [], 'id');
+    if (fid.includes('eventNotModifiable')) {
+      obj.events = old?.event || [];
+    }
+    if (fid.includes('propertyNotModifiable')) {
+      obj.properties = old?.properties || {};
+    }
+    return obj;
+  };
+
   const handleImport = async () => {
     const data = (await form.submit()) as any;
 
@@ -188,14 +204,16 @@ const Import = (props: Props) => {
       service.convertMetadata('from', 'alink', data.import).subscribe({
         next: async (meta) => {
           onlyMessage('导入成功');
-          await service.modify(param.id, { metadata: JSON.stringify(meta) });
+          await service.modify(param.id, { metadata: JSON.stringify(operateLimits(meta)) });
         },
         error: () => {
           onlyMessage('发生错误!', 'error');
         },
       });
     } else {
-      const resp = await service.modify(param.id, { metadata: data[data.type] });
+      const resp = await service.modify(param.id, {
+        metadata: JSON.stringify(operateLimits(JSON.parse(data[data?.type] || '{}'))),
+      });
       if (resp.status === '200') {
         onlyMessage('导入成功');
       }

+ 5 - 4
src/pages/home/components/Steps.tsx

@@ -7,13 +7,13 @@ interface StepItemProps {
   content: string | React.ReactNode;
   onClick: () => void;
   url?: string;
-  after?: any
+  after?: any;
 }
 
 interface StepsProps {
   title: string | React.ReactNode;
   data: StepItemProps[];
-  style?: any
+  style?: any;
 }
 
 const ItemDefaultImg = require('/public/images/home/bottom-1.png');
@@ -40,10 +40,11 @@ const Steps = (props: StepsProps) => {
         style={{
           gridTemplateColumns: `repeat(${props.data ? props.data.length : 1}, 1fr)`,
           minHeight: props.style?.height,
-          gridColumnGap:props?.style?.gridColumnGap
+          gridColumnGap: props?.style?.gridColumnGap,
         }}
       >
-        {props.data && props.data.map((item) => <StepsItem {...item} after={props.style ? true : false} />)}
+        {props.data &&
+          props.data.map((item) => <StepsItem {...item} after={props.style ? true : false} />)}
       </div>
     </div>
   );

+ 4 - 4
src/pages/home/comprehensive/index.tsx

@@ -95,7 +95,7 @@ const Comprehensive = () => {
       img: require('/public/images/home/guide-home1.png'),
       english: 'CREATE PRODUCT',
       auth: !!productPermission.add,
-      url: 'device/Product',
+      url: getMenuPathByCode('device/Product'),
       param: {
         save: true,
       },
@@ -106,7 +106,7 @@ const Comprehensive = () => {
       english: 'CREATE DEVICE',
       img: require('/public/images/home/guide-home2.png'),
       auth: !!devicePermission.add,
-      url: 'device/Instance',
+      url: getMenuPathByCode('device/Instance'),
       param: {
         save: true,
       },
@@ -117,7 +117,7 @@ const Comprehensive = () => {
       english: 'RULE ENGINE',
       img: require('/public/images/home/guide-home3.png'),
       auth: !!rulePermission.add,
-      url: 'rule-engine/Instance',
+      url: getMenuPathByCode('rule-engine/Instance'),
       param: {
         save: true,
       },
@@ -171,7 +171,7 @@ const Comprehensive = () => {
                 {
                   name: '产品数量',
                   value: productCount,
-                  children:require('/public/images/home/top-2.png'),
+                  children: require('/public/images/home/top-2.png'),
                 },
                 {
                   name: '设备数量',

+ 41 - 0
src/pages/home/device/index.tsx

@@ -141,6 +141,8 @@ const Device = () => {
                 '产品是设备的集合,通常指一组具有相同功能的设备。物联设备必须通过产品进行接入方式配置。',
               onClick: () => {
                 const path = getMenuPathByCode('device/Product');
+                console.log(path);
+
                 if (path && !!productPermission.add) {
                   history.push(`${path}`, {
                     save: true,
@@ -204,6 +206,45 @@ const Device = () => {
           ]}
         />
       </Col>
+      <Col span={6}>
+        <Statistics
+          style={{ gridTemplateColumns: 'repeat(1, 1fr)' }}
+          // height={448}
+          data={[
+            {
+              name: '产品数量',
+              value: productCount,
+              children: '',
+            },
+            {
+              name: '设备数量',
+              value: deviceCount,
+              children: '',
+            },
+          ]}
+          title="设备统计"
+          extra={
+            <div style={{ fontSize: 14, fontWeight: 400 }}>
+              <a
+                onClick={() => {
+                  const url = getMenuPathByCode(MENUS_CODE['device/DashBoard']);
+                  if (!!url) {
+                    history.push(`${url}`);
+                  } else {
+                    message.warning('暂无权限,请联系管理员');
+                  }
+                }}
+              >
+                详情
+              </a>
+            </div>
+          }
+        />
+      </Col>
+      <Col span={24} style={{ marginTop: 24 }}>
+        <Body title={'平台架构图'} english={'PLATFORM ARCHITECTURE DIAGRAM'} />
+      </Col>
+
       <ProductChoose
         visible={productVisible}
         close={() => {

+ 94 - 0
src/pages/link/AccessConfig/Detail/Media/SipSelectComponent/index.tsx

@@ -0,0 +1,94 @@
+import { Select } from 'antd';
+import { useEffect, useState } from 'react';
+
+interface SipSelectComponentProps {
+  onChange?: (data: any) => void;
+  value?: {
+    host?: string;
+    port?: number;
+  };
+  type?: boolean;
+  data: any[];
+}
+
+const SipSelectComponent = (props: SipSelectComponentProps) => {
+  const { value, onChange } = props;
+  const [data, setData] = useState<{ host?: string; port?: number } | undefined>(value);
+  const [list, setList] = useState<any[]>([]);
+
+  useEffect(() => {
+    setData(value);
+  }, [value]);
+
+  useEffect(() => {
+    if (!props.type) {
+      setData(value || { host: '0.0.0.0' });
+      if (!value) {
+        const dt: any = props.data.find((i) => i.host === '0.0.0.0');
+        setList(dt?.portList || []);
+      } else {
+        const dt: any = props.data.find((i) => i.host === value.host);
+        setList(dt?.portList || []);
+      }
+    }
+  }, [props.type]);
+
+  return (
+    <div style={{ display: 'flex', alignItems: 'center' }}>
+      <Select
+        showSearch
+        value={data?.host}
+        style={{ marginRight: 10 }}
+        placeholder="请选择IP地址"
+        disabled={!props.type}
+        onChange={(e) => {
+          if (onChange) {
+            const item = {
+              port: undefined,
+              host: e,
+            };
+            onChange(item);
+            const dt: any = props.data.find((i) => i.host === e);
+            setList(dt?.portList || []);
+          }
+        }}
+        filterOption={(input: string, option: any) =>
+          String(option?.children)?.toLowerCase()?.indexOf(String(input).toLowerCase()) >= 0
+        }
+      >
+        {(props.data || []).map((item: any) => (
+          <Select.Option key={item.host} value={item.host}>
+            {item.host}
+          </Select.Option>
+        ))}
+      </Select>
+      <Select
+        showSearch
+        style={{ minWidth: 100 }}
+        value={data?.port}
+        placeholder="请选择端口"
+        optionFilterProp="children"
+        onChange={(e: number) => {
+          if (onChange) {
+            const item = {
+              ...data,
+              port: e,
+            };
+            onChange(item);
+          }
+        }}
+        filterOption={(input: string, option: any) =>
+          String(option?.children)?.toLowerCase()?.indexOf(String(input).toLowerCase()) >= 0
+        }
+      >
+        {list.map((item) => (
+          <Select.Option key={item?.port} value={item?.port}>
+            {(item?.transports || []).join('/')}({item?.port})
+          </Select.Option>
+        ))}
+      </Select>
+    </div>
+  );
+};
+
+export default SipSelectComponent;

+ 25 - 3
src/pages/link/AccessConfig/Detail/Media/index.tsx

@@ -22,6 +22,7 @@ import TitleComponent from '@/components/TitleComponent';
 import { InfoCircleOutlined } from '@ant-design/icons';
 import { onlyMessage, testIP } from '@/utils/util';
 import { getButtonPermission } from '@/utils/menu';
+import SipSelectComponent from './SipSelectComponent';
 
 type LocationType = {
   id?: string;
@@ -38,6 +39,7 @@ const Media = (props: Props) => {
   const [form] = Form.useForm();
   const [configuration, setConfiguration] = useState<any>({});
   const [clusters, setClusters] = useState<any[]>([]);
+  const [alist, setAList] = useState<any[]>([]);
 
   const location = useLocation<LocationType>();
 
@@ -88,6 +90,16 @@ const Media = (props: Props) => {
     }
   };
 
+  useEffect(() => {
+    if (props.provider?.id === 'gb28181-2016') {
+      service.getResourcesCurrent().then((resp) => {
+        if (resp.status === 200) {
+          setAList(resp.result);
+        }
+      });
+    }
+  }, [props.provider]);
+
   const BasicRender = () => {
     const SchemaField = createSchemaField({
       components: {
@@ -96,6 +108,7 @@ const Media = (props: Props) => {
         Select,
         Radio,
         SipComponent,
+        SipSelectComponent,
         FormGrid,
         FormCollapse,
         ArrayCollapse,
@@ -146,6 +159,7 @@ const Media = (props: Props) => {
       },
       properties: {
         clusterNodeId: {
+          type: 'string',
           title: '节点名称',
           'x-component': 'Select',
           'x-decorator': 'FormItem',
@@ -159,13 +173,17 @@ const Media = (props: Props) => {
         sip: {
           title: 'SIP 地址',
           'x-decorator': 'FormItem',
-          'x-component': 'SipComponent',
+          'x-component': 'SipSelectComponent',
           'x-decorator-props': {
             gridSpan: 1,
             labelAlign: 'left',
             layout: 'vertical',
             tooltip: '绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0',
           },
+          'x-component-props': {
+            data: alist,
+            type: true,
+          },
           'x-validator': [
             {
               required: true,
@@ -185,7 +203,6 @@ const Media = (props: Props) => {
             layout: 'vertical',
           },
           required: true,
-          type: 'number',
           'x-decorator': 'FormItem',
           'x-component': 'SipComponent',
           'x-validator': [
@@ -297,9 +314,13 @@ const Media = (props: Props) => {
                   properties: {
                     sip: {
                       title: 'SIP 地址',
-                      'x-component': 'SipComponent',
+                      'x-component': 'SipSelectComponent',
                       'x-decorator': 'FormItem',
                       required: true,
+                      'x-component-props': {
+                        data: alist,
+                        type: false,
+                      },
                       'x-decorator-props': {
                         gridSpan: 1,
                         labelAlign: 'left',
@@ -405,6 +426,7 @@ const Media = (props: Props) => {
               <Button
                 onClick={async () => {
                   const value = await aform.submit<any>();
+                  console.log(value);
                   let param: any = {};
                   if (value.configuration.shareCluster) {
                     const hostPort = { ...value.configuration.hostPort };

+ 4 - 0
src/pages/link/AccessConfig/service.ts

@@ -65,6 +65,10 @@ class Service extends BaseService<AccessItem> {
     request(`/${SystemConst.API_BASE}/user/settings/product/guide`, {
       method: 'DELETE',
     })
+  public getResourcesCurrent = () =>
+    request(`${SystemConst.API_BASE}/network/resources/alive/_current`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

+ 22 - 22
src/pages/link/Protocol/save/index.tsx

@@ -20,30 +20,30 @@ interface Props {
 const Save = (props: Props) => {
   const [data, setData] = useState<ProtocolItem | undefined>(props.data);
   const { permission } = PermissionButton.usePermission('link/Protocol');
-  const [count, setCount] = useState<number>(0);
+  // const [count, setCount] = useState<number>(0);
 
   useEffect(() => {
     setData(props.data);
-    if (props.data?.id) {
-      service
-        .productCount({
-          terms: [
-            {
-              terms: [
-                {
-                  column: 'message_protocol',
-                  value: props.data.id,
-                },
-              ],
-            },
-          ],
-        })
-        .then((resp) => {
-          if (resp.status === 200) {
-            setCount(resp.result);
-          }
-        });
-    }
+    // if (props.data?.id) {
+    //   service
+    //     .productCount({
+    //       terms: [
+    //         {
+    //           terms: [
+    //             {
+    //               column: 'message_protocol',
+    //               value: props.data.id,
+    //             },
+    //           ],
+    //         },
+    //       ],
+    //     })
+    //     .then((resp) => {
+    //       if (resp.status === 200) {
+    //         setCount(resp.result);
+    //       }
+    //     });
+    // }
   }, [props.data]);
 
   const form = createForm({
@@ -200,7 +200,7 @@ const Save = (props: Props) => {
                 'x-decorator-props': {
                   gridSpan: 2,
                 },
-                'x-disabled': !!count,
+                // 'x-disabled': !!count,
                 'x-validator': [
                   {
                     required: true,

+ 6 - 0
src/pages/link/Type/Detail/index.less

@@ -7,3 +7,9 @@
     border-top: none;
   }
 }
+
+.parser {
+  width: 100%;
+  padding: 16px;
+  background-color: #fafafa;
+}

+ 237 - 66
src/pages/link/Type/Detail/index.tsx

@@ -15,9 +15,9 @@ import {
 } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
 import { useEffect, useMemo, useRef } from 'react';
-import { Field, FieldDataSource } from '@formily/core';
+import type { Field, FieldDataSource } from '@formily/core';
 import { createForm, onFieldReact, onFieldValueChange } from '@formily/core';
-import { Card } from 'antd';
+import { Card, Col, Row } from 'antd';
 import styles from './index.less';
 import { onlyMessage, useAsyncDataSource } from '@/utils/util';
 import { service } from '../index';
@@ -27,6 +27,7 @@ import { Store } from 'jetlinks-store';
 import { PermissionButton } from '@/components';
 import usePermissions from '@/hooks/permission';
 import { action } from '@formily/reactive';
+import FMonacoEditor from '@/components/FMonacoEditor';
 
 /**
  *  根据类型过滤配置信息
@@ -187,6 +188,16 @@ const Save = observer(() => {
               state.dataSource = _ports?.ports?.map((i: any) => ({ label: i, value: i }));
             });
           });
+          // onFieldValueChange('grid.cluster.cluster.*.layout2.host', async (field, f4) => {
+          //   const host = (field as Field).value;
+          //   const value = (field.query('.serverId').take() as Field).value;
+          //   const type = (field.query('type').take() as Field).value;
+          //   const response = await getResourceById(value, type);
+          //   const _ports = response.find((item) => item.host === host);
+          //   f4.setFieldState(field.query('.port').take(), async (state) => {
+          //     state.dataSource = _ports?.ports?.map((i: any) => ({ label: i, value: i }));
+          //   });
+          // });
         },
       }),
     [],
@@ -221,6 +232,7 @@ const Save = observer(() => {
       FormCollapse,
       ArrayCollapse,
       FAutoComplete,
+      FMonacoEditor,
     },
   });
 
@@ -233,12 +245,11 @@ const Save = observer(() => {
         value: item.id,
       })),
     );
-
   const clusterConfig: ISchema = {
     type: 'void',
     'x-component': 'FormGrid',
     'x-component-props': {
-      maxColumns: 3,
+      maxColumns: 2,
       minColumns: 1,
       columnGap: 48,
     },
@@ -528,44 +539,12 @@ const Save = observer(() => {
           },
         },
       },
-      parserType: {
-        // TCP
-        required: true,
-        title: '粘拆包规则',
-        'x-decorator-props': {
-          gridSpan: 1,
-          tooltip: '处理TCP粘拆包的方式',
-          layout: 'vertical',
-          labelAlign: 'left',
-        },
-        'x-visible': false,
-        'x-decorator': 'FormItem',
-        'x-component': 'Select',
-        'x-component-props': {
-          placeholder: '请选择粘拆包规则',
-        },
-        enum: [
-          { value: 'DIRECT', label: '不处理' },
-          { value: 'delimited', label: '分隔符' },
-          { value: 'script', label: '自定义脚本' },
-          { value: 'fixed_length', label: '固定长度' },
-        ],
-        'x-reactions': {
-          dependencies: ['type'],
-          fulfill: {
-            state: {
-              // visible: '{{$deps[0]==="UDP"}}',
-              visible: '{{["TCP_SERVER"].includes($deps[0])}}',
-            },
-          },
-        },
-      },
       secure: {
         title: '开启DTLS',
         'x-decorator': 'FormItem',
         'x-component': 'Radio.Group',
         'x-decorator-props': {
-          gridSpan: 1,
+          gridSpan: 2,
           labelAlign: 'left',
           layout: 'vertical',
         },
@@ -633,6 +612,190 @@ const Save = observer(() => {
           },
         },
       },
+      parserType: {
+        // TCP
+        required: true,
+        title: '粘拆包规则',
+        'x-decorator-props': {
+          gridSpan: 2,
+          tooltip: '处理TCP粘拆包的方式',
+          layout: 'vertical',
+          labelAlign: 'left',
+        },
+        'x-visible': false,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请选择粘拆包规则',
+          style: { width: 'calc(50% - 24px)' },
+        },
+        enum: [
+          { value: 'DIRECT', label: '不处理' },
+          { value: 'delimited', label: '分隔符' },
+          { value: 'script', label: '自定义脚本' },
+          { value: 'fixed_length', label: '固定长度' },
+        ],
+        'x-reactions': {
+          dependencies: ['type'],
+          fulfill: {
+            state: {
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible: '{{["TCP_SERVER"].includes($deps[0])}}',
+            },
+          },
+        },
+      },
+      parserConfiguration: {
+        type: 'object',
+        'x-component': 'FormGrid',
+        'x-decorator': 'FormItem',
+        'x-component-props': {
+          maxColumns: 2,
+          minColumns: 1,
+          className: styles.parser,
+        },
+        'x-decorator-props': {
+          gridSpan: 2,
+        },
+        'x-reactions': [
+          {
+            dependencies: ['.parserType'],
+            fulfill: {
+              state: {
+                visible: '{{["delimited", "script", "fixed_length"].includes($deps[0])}}',
+              },
+            },
+          },
+        ],
+        properties: {
+          delimited: {
+            title: '分隔符',
+            'x-component': 'Input',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              labelAlign: 'left',
+              layout: 'vertical',
+              gridSpan: 1,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入分隔符',
+              },
+            ],
+            'x-component-props': {
+              style: { width: '50%' },
+              placeholder: '请输入分隔符',
+            },
+            'x-reactions': [
+              {
+                dependencies: ['..parserType'],
+                fulfill: {
+                  state: {
+                    visible: '{{$deps[0] === "delimited"}}',
+                  },
+                },
+              },
+            ],
+          },
+          lang: {
+            title: '脚本语言',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            'x-disabled': true,
+            'x-hidden': true,
+            'x-value': 'javascript',
+            'x-decorator-props': {
+              labelAlign: 'left',
+              layout: 'vertical',
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择',
+              },
+            ],
+            'x-reactions': [
+              {
+                dependencies: ['..parserType'],
+                fulfill: {
+                  state: {
+                    // visible: '{{$deps[0] === "script"}}',
+                    value: '{{$deps[0] === "script" ? "javascript" : ""}}',
+                  },
+                },
+              },
+            ],
+            enum: [{ label: 'JavaScript', value: 'javascript' }],
+          },
+          script: {
+            title: '脚本解析',
+            'x-component': 'FMonacoEditor',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              labelAlign: 'left',
+              layout: 'vertical',
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              language: 'javascript',
+              height: 200,
+              editorDidMount: (editor1: any) => {
+                editor1.onDidScrollChange?.(() => {
+                  editor1.getAction('editor.action.formatDocument').run();
+                });
+              },
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入脚本',
+              },
+            ],
+            'x-reactions': [
+              {
+                dependencies: ['..parserType'],
+                fulfill: {
+                  state: {
+                    visible: '{{$deps[0] === "script"}}',
+                  },
+                },
+              },
+            ],
+          },
+          size: {
+            title: '长度值',
+            'x-component': 'NumberPicker',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              labelAlign: 'left',
+              layout: 'vertical',
+              gridSpan: 1,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入长度值',
+              },
+            ],
+            'x-component-props': {
+              style: { width: '50%' },
+              placeholder: '请输入长度值',
+            },
+            'x-reactions': [
+              {
+                dependencies: ['..parserType'],
+                fulfill: {
+                  state: {
+                    visible: '{{$deps[0] === "fixed_length"}}',
+                  },
+                },
+              },
+            ],
+          },
+        },
+      },
     },
   };
   const schema: ISchema = {
@@ -642,7 +805,7 @@ const Save = observer(() => {
         type: 'void',
         'x-component': 'FormGrid',
         'x-component-props': {
-          maxColumns: 3,
+          maxColumns: 2,
           minColumns: 1,
           columnGap: 48,
         },
@@ -698,8 +861,12 @@ const Save = observer(() => {
               { label: '共享配置', value: true },
               { label: '独立配置', value: false },
             ],
+            'x-component-props': {
+              buttonStyle: 'solid',
+              optionType: 'button',
+            },
             'x-decorator-props': {
-              gridSpan: 3,
+              gridSpan: 2,
               tooltip:
                 '共享配置:集群下所有节点共用同一配置\r\n' + '独立配置:集群下不同节点使用不同配置',
             },
@@ -724,7 +891,7 @@ const Save = observer(() => {
               },
             ],
             'x-decorator-props': {
-              gridSpan: 3,
+              gridSpan: 2,
             },
             properties: {
               panel1: {
@@ -740,7 +907,7 @@ const Save = observer(() => {
             type: 'void',
             'x-decorator': 'FormItem',
             'x-decorator-props': {
-              gridSpan: 3,
+              gridSpan: 2,
             },
             'x-reactions': {
               dependencies: ['.shareCluster', 'type'],
@@ -789,7 +956,7 @@ const Save = observer(() => {
             'x-component': 'Input.TextArea',
             'x-decorator': 'FormItem',
             'x-decorator-props': {
-              gridSpan: 3,
+              gridSpan: 2,
             },
             'x-component-props': {
               placeholder: '请输入说明',
@@ -830,30 +997,34 @@ const Save = observer(() => {
   return (
     <PageContainer>
       <Card>
-        <Form form={form} layout="vertical" style={{ padding: 30 }}>
-          <SchemaField
-            schema={schema}
-            scope={{
-              formCollapse,
-              useAsyncDataSource,
-              useAsyncData,
-              getSupports,
-              getResourcesClusters,
-              getCertificates,
-            }}
-          />
-          <FormButtonGroup.Sticky>
-            <FormButtonGroup.FormItem>
-              <PermissionButton
-                type="primary"
-                isPermission={getOtherPermission(['add', 'update'])}
-                onClick={() => handleSave()}
-              >
-                保存
-              </PermissionButton>
-            </FormButtonGroup.FormItem>
-          </FormButtonGroup.Sticky>
-        </Form>
+        <Row gutter={24}>
+          <Col span={16}>
+            <Form form={form} layout="vertical" style={{ padding: 30 }}>
+              <SchemaField
+                schema={schema}
+                scope={{
+                  formCollapse,
+                  useAsyncDataSource,
+                  useAsyncData,
+                  getSupports,
+                  getResourcesClusters,
+                  getCertificates,
+                }}
+              />
+              <FormButtonGroup.Sticky>
+                <FormButtonGroup.FormItem>
+                  <PermissionButton
+                    type="primary"
+                    isPermission={getOtherPermission(['add', 'update'])}
+                    onClick={() => handleSave()}
+                  >
+                    保存
+                  </PermissionButton>
+                </FormButtonGroup.FormItem>
+              </FormButtonGroup.Sticky>
+            </Form>
+          </Col>
+        </Row>
       </Card>
     </PageContainer>
   );

+ 43 - 33
src/pages/rule-engine/Scene/Save/Explanation.tsx

@@ -2,7 +2,7 @@
 import styles from './Explanation.less';
 import { ExclamationCircleFilled } from '@ant-design/icons';
 
-export default () => {
+export default ({ type }: { type: string }) => {
   return (
     <div className={styles.doc} style={{ marginLeft: 12 }}>
       <h1>1.概述</h1>
@@ -11,39 +11,42 @@ export default () => {
       </div>
       <div>场景联动的规则可用于告警规则的引用,达到触发条件时将生成对应的告警记录。</div>
       <h1>2.配置说明</h1>
-      <h2>1、触发方式</h2>
-      <div>
-        包含<b>手动触发</b>、<b>定时触发</b>、<b>设备触发</b>3种方式
-      </div>
-      <h3>a. 手动触发</h3>
-      <div>适用于第三方平台向物联网平台下发指令控制设备。</div>
-      <div>
-        <b>例如:</b>用户通过智能家居APP开启或关闭设备。
-      </div>
-
-      <h3>b. 定时触发</h3>
-      <div>
-        <b>例如:</b>每天早上9点打开空调,并将空调开到26度。
-      </div>
+      {type === 'manual' && (
+        <>
+          <h2>1. 手动触发</h2>
+          <div>适用于第三方平台向物联网平台下发指令控制设备。</div>
+          <div>
+            <b>例如:</b>用户通过智能家居APP开启或关闭设备。
+          </div>
+        </>
+      )}
+      {type === 'timer' && (
+        <>
+          <h2>1. 定时触发</h2>
+          <div>
+            适用于第三方平台向物联网平台下发指令控制设备。支持按周、按月、按Corn表达式3种方式配置频率。
+          </div>
+          <div>
+            <b>例如:</b>每天早上9点打开空调,并将空调开到26度。
+          </div>
+        </>
+      )}
+      {type === 'device' && (
+        <>
+          <h2>1. 设备触发</h2>
+          <div>
+            适用于多个不同设备间执行动作的联动。选择具体设备时支持指定产品下属的固定设备、全部设备或选择产品下归属于具体部门的设备
+          </div>
+          <div>
+            <b>例如:</b>打开综合办部门房间门的时候,打开电灯、空调。
+          </div>
+        </>
+      )}
 
-      <h3>c. 设备触发</h3>
+      <h2>2.执行动作:</h2>
       <div>
-        适用于多个不同设备间执行动作的联动。选择具体设备时支持指定产品下属的固定设备、全部设备或选择产品下归属于具体部门的设备
+        支持消息通知、设备输出、延迟执行3种方式,延迟执行只在执行动作为串行时显示。另外串行模式下,多个执行动作间可以通过条件过滤判断时候继续执行后续动作。
       </div>
-      <div>
-        <b>例如:</b>打开综合办部门房间门的时候,打开电灯、空调。
-      </div>
-
-      <h2>2、触发条件</h2>
-      <div>
-        选择设备触发时,可对设备触发条件进行详细配置。触发条件支持多组条件,条件之间支持并且/或者的关系配置。每一个分组内可以添加多个条件,条件之间支持并且/或者的关系配置。
-      </div>
-      <div>
-        触发条件支持以属性指标(需在产品物模型tab页进行属性指标定义,定义后在对应设备的运行状态页填写阈值)作为判断阈值。
-      </div>
-
-      <h2>3、执行动作</h2>
-      <div>支持消息通知、设备输出、延迟执行3种方式,延迟执行只在执行动作为串行时显示。</div>
       <h3>a.消息通知</h3>
       <div>
         通过系统外的渠道给相关人员发送对应的通知模板内容。若钉钉消息、微信企业消息的通知模板中没有定义固定通知人,可在当前页面进行配置。同时“收信人”字段支持同步平台与微信、钉钉用户映射关系(对应类型的通知配置列表页配置),实现选择平台用户即可通知到对应的微信、钉钉用户。
@@ -57,7 +60,7 @@ export default () => {
         设备下线时,以微信或钉钉的方式通知该“设备负责人”(需管理员先在“关系配置”菜单中定义设备与用户的关系,然后到设备实例信息页面填写值)。
       </div>
 
-      <h3>b.设备输出</h3>
+      <h3>b.设备输出:</h3>
       <div>
         联动其他设备进行功能调用、读取属性、设置属性操作。
         选择设备时支持固定设备、按标签(需在设备物模型中自定义配置)、按关系(需管理员先在“关系配置”菜单中定义设备与用户的关系,然后到设备实例信息页面填写值)3种方式。
@@ -71,9 +74,16 @@ export default () => {
         {`温度传感器>30度时,与该设备相同“设备负责人”(自定义关系名)的空调执行打开操作。即可实现张三的温度传感器>30度时自动打开张三的空调,李四的温度传感器>30度时自动打开李四的空调。`}
       </div>
 
-      <h3>c.延迟执行</h3>
+      <h3>c.延迟执行:</h3>
       <div>设置延时时间后,设备执行动作将保持当前状态,时间结束后再执行下一条动作。</div>
       <div>房间门打开时,摄像头开启录像,延迟执行5s后,停止录像。</div>
+
+      <h3>d.条件过滤:</h3>
+      <div>根据当前动作执行是否成功,判断是否执行后续动作</div>
+      <div>
+        <b>例如:</b>
+        房间门打开时,执行打开空调,如果打开空调失败,给维修人员发送微信通知。
+      </div>
     </div>
   );
 };

+ 31 - 34
src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx

@@ -32,40 +32,6 @@ export default (props: BuiltInProps) => {
 
   const [builtInList, setBuiltInList] = useState<any[]>([]);
 
-  useEffect(() => {
-    console.log(props.parallel);
-    // if (source === 'upper' && props.trigger) {
-    //   getBuiltInList({ ...props.trigger });
-    // }
-    if (source === 'upper') {
-      if (props.parallel === false) {
-        const data = props.form.getFieldsValue();
-        const params = props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
-        queryBuiltInParams(data, params).then((res: any) => {
-          if (res.status === 200) {
-            const actionParams = res.result.filter(
-              (item: any) => item.id === `action_${props.name}`,
-            );
-            const _data = props.name === 0 ? res.result : BuiltInParamsHandleTreeData(actionParams);
-            setBuiltInList(_data);
-          }
-        });
-      } else {
-        queryBuiltInParams({ ...props.trigger }).then((res: any) => {
-          if (res.status === 200) {
-            setBuiltInList(BuiltInParamsHandleTreeData(res.result));
-          }
-        });
-      }
-    }
-  }, [source, props.trigger, props.parallel]);
-
-  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,
@@ -81,6 +47,37 @@ export default (props: BuiltInProps) => {
     }
   };
 
+  const sourceChangeEvent = () => {
+    onChange(source, undefined);
+    const data = props.form.getFieldsValue();
+    const params = props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
+    queryBuiltInParams(data, params).then((res: any) => {
+      if (res.status === 200) {
+        const actionParams = res.result.filter((item: any) => item.id === `action_${props.name}`);
+        const _data = props.name === 0 ? res.result : BuiltInParamsHandleTreeData(actionParams);
+        setBuiltInList(_data);
+      }
+    });
+  };
+
+  useEffect(() => {
+    if (source === 'upper') {
+      sourceChangeEvent();
+    }
+  }, [source, props.trigger, props.parallel]);
+
+  useEffect(() => {
+    if (props.trigger?.trigger?.device?.productId && source === 'upper') {
+      sourceChangeEvent();
+    }
+  }, [props.trigger?.trigger?.device?.productId, source]);
+
+  useEffect(() => {
+    setSource(props.value?.source);
+    setValue(props.value?.value);
+    setUpperKey(props.value?.upperKey);
+  }, [props.value]);
+
   const itemOnChange = useCallback(
     (_value: any) => {
       onChange(source, _value);

+ 42 - 10
src/pages/rule-engine/Scene/Save/action/action.tsx

@@ -34,8 +34,6 @@ interface ActionProps {
 
 export default observer((props: ActionProps) => {
   const { name } = props;
-  console.log('name', name);
-  console.log('isLast', props.isLast);
   const [type1, setType1] = useState('');
   // 消息通知
   const [notifyType, setNotifyType] = useState('');
@@ -47,6 +45,8 @@ export default observer((props: ActionProps) => {
   const [properties, setProperties] = useState([]); // 物模型-属性
   const [functionList, setFunctionList] = useState([]); // 物模型-功能
 
+  const [productId, setProductId] = useState('');
+
   const [isFiltering, setIsFiltering] = useState(false);
 
   const { data: messageType, run: queryMessageTypes } = useRequest(queryMessageType, {
@@ -123,11 +123,10 @@ export default observer((props: ActionProps) => {
 
   const handleInit = useCallback(async (data: any) => {
     if (data) {
-      console.log('actions ', data);
       if (data.executor) {
         setType1(data.executor);
       }
-      console.log(data.terms);
+
       if (data.terms) {
         // 显示过滤条件
         setIsFiltering(true);
@@ -146,6 +145,26 @@ export default observer((props: ActionProps) => {
     handleInit(props.actionItemData);
   }, [props.actionItemData]);
 
+  useEffect(() => {
+    if (
+      props.actionItemData?.executor &&
+      props.actionItemData?.executor === 'delay' &&
+      props.parallel === true
+    ) {
+      props.onRemove();
+    }
+  }, [props.parallel]);
+
+  useEffect(() => {
+    if (productId) {
+      const actions = props.form.getFieldValue('actions');
+      if (actions[name].device?.message?.properties) {
+        actions[name].device.message.properties = undefined;
+        props.form.setFieldsValue({ actions });
+      }
+    }
+  }, [productId]);
+
   const MessageNodes = (
     <>
       <Col span={7}>
@@ -276,11 +295,18 @@ export default observer((props: ActionProps) => {
             rules={[{ required: true, message: '请选择执行条件' }]}
           >
             <Select
-              options={[
-                { label: '消息通知', value: 'notify' },
-                { label: '设备输出', value: 'device' },
-                { label: '延迟执行', value: 'delay' },
-              ]}
+              options={
+                !props.parallel
+                  ? [
+                      { label: '消息通知', value: 'notify' },
+                      { label: '设备输出', value: 'device' },
+                      { label: '延迟执行', value: 'delay' },
+                    ]
+                  : [
+                      { label: '消息通知', value: 'notify' },
+                      { label: '设备输出', value: 'device' },
+                    ]
+              }
               placeholder={'请选择执行条件'}
               style={{ width: '100%' }}
             />
@@ -298,6 +324,7 @@ export default observer((props: ActionProps) => {
             onFunctionChange={setFunctionList}
             restField={props.restField}
             parallel={props.parallel}
+            onProductIdChange={setProductId}
           />
         )}
         {type1 === 'delay' && (
@@ -348,8 +375,10 @@ export default observer((props: ActionProps) => {
                   name={name}
                   properties={properties}
                   type={props.triggerType}
+                  trigger={props.trigger}
                   form={props.form}
                   parallel={props.parallel}
+                  productId={productId}
                 />
               </Form.Item>
             </Col>
@@ -361,6 +390,7 @@ export default observer((props: ActionProps) => {
                 name={name}
                 form={props.form}
                 data={props.actionItemData.terms}
+                productId={productId}
               />
             </Row>
           )}
@@ -387,6 +417,7 @@ export default observer((props: ActionProps) => {
                 name={name}
                 form={props.form}
                 data={props.actionItemData.terms}
+                productId={productId}
               />
             </Row>
           )}
@@ -400,7 +431,7 @@ export default observer((props: ActionProps) => {
             name={[name, 'device', 'message', 'inputs']}
             rules={[{ required: true, message: '请输入功能值' }]}
           >
-            <FunctionCall functionData={functionList} />
+            <FunctionCall functionData={functionList} productId={productId} />
           </Form.Item>
           <Row gutter={24}>
             {parallelNode}
@@ -409,6 +440,7 @@ export default observer((props: ActionProps) => {
                 name={name}
                 form={props.form}
                 data={props.actionItemData.terms}
+                productId={productId}
               />
             )}
           </Row>

+ 45 - 5
src/pages/rule-engine/Scene/Save/action/device/ConditionalFiltering.tsx

@@ -8,6 +8,7 @@ interface ConditionalFilteringProps {
   name: number;
   form: FormInstance;
   data?: any;
+  productId: string;
 }
 
 export default (props: ConditionalFilteringProps) => {
@@ -85,7 +86,6 @@ export default (props: ConditionalFilteringProps) => {
   const getBuiltItemById = useCallback(
     (id: string) => {
       const builtItem: any = getItemByChildren(id, builtInList);
-      console.log(builtItem, id);
       if (builtItem) {
         setType(builtItem.type);
         setTermTypes(builtItem.termTypes);
@@ -95,7 +95,7 @@ export default (props: ConditionalFilteringProps) => {
     [builtInList],
   );
 
-  useEffect(() => {
+  const getBuiltInParamsData = () => {
     const data = props.form.getFieldsValue();
     queryBuiltInParams(data, { action: props.name }).then((res: any) => {
       if (res.status === 200) {
@@ -105,16 +105,32 @@ export default (props: ConditionalFilteringProps) => {
         setBuiltInList(handleTreeData(actionParams));
       }
     });
-  }, []);
+  };
 
   useEffect(() => {
-    console.log('Conditional', builtInList);
     if (props.data && props.data[0] && props.data[0].column && builtInList && builtInList.length) {
       getBuiltItemById(props.data[0].column);
     }
   }, [props.data, builtInList]);
 
-  console.log(props.data, props.name);
+  useEffect(() => {
+    if (props.productId) {
+      getBuiltInParamsData();
+      const actions = props.form.getFieldValue('actions');
+      if (actions?.[props.name].terms?.[0]) {
+        actions[props.name].terms[0] = {
+          column: undefined,
+          termType: undefined,
+          value: {
+            source: 'fixed',
+            value: undefined,
+          },
+        };
+      }
+      setSource('fixed');
+    }
+  }, [props.productId]);
+
   return (
     <>
       <Col span={4}>
@@ -175,6 +191,30 @@ export default (props: ConditionalFilteringProps) => {
                 style={{ width: 120 }}
                 onSelect={(v: any) => {
                   setSource(v);
+                  if (
+                    ['nbtw', 'btw'].includes(
+                      props.data && props.data[0] && props.data[0].termType,
+                    ) &&
+                    source === 'fixed'
+                  ) {
+                    props.form.setFields([
+                      {
+                        name: [props.name, 'terms', 0, 'value', 'value', 0],
+                        value: undefined,
+                      },
+                      {
+                        name: [props.name, 'terms', 0, 'value', 'value', 1],
+                        value: undefined,
+                      },
+                    ]);
+                  } else {
+                    props.form.setFields([
+                      {
+                        name: [props.name, 'terms', 0, 'value', 'value'],
+                        value: undefined,
+                      },
+                    ]);
+                  }
                 }}
               />
             </Form.Item>

+ 63 - 38
src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx

@@ -14,6 +14,8 @@ interface WritePropertyProps {
   onChange?: (value?: any) => void;
   parallel?: boolean;
   name: number;
+  trigger?: any;
+  productId: string;
 }
 
 export default (props: WritePropertyProps) => {
@@ -51,57 +53,80 @@ export default (props: WritePropertyProps) => {
     if (props.onChange) {
       props.onChange({
         [key || 0]: {
-          value,
+          value: value,
           source: _source,
         },
       });
     }
   };
 
+  const sourceChangeEvent = () => {
+    onChange(propertiesKey, undefined, source);
+    const params = props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
+    const data = props.form.getFieldsValue();
+    queryBuiltInParams(data, params).then((res: any) => {
+      if (res.status === 200) {
+        const actionParams = res.result.filter((item: any) => item.id === `action_${props.name}`);
+        const _data = props.name === 0 ? res.result : handleTreeData(actionParams);
+        setBuiltInList(_data);
+      }
+    });
+    // if (props.parallel === false) {
+    //   // 串行
+    // } else {
+    //   // 并行
+    //   queryBuiltInParams({
+    //     trigger: { type: props.type },
+    //   }).then((res: any) => {
+    //     if (res.status === 200) {
+    //       setBuiltInList(handleTreeData(res.result));
+    //     }
+    //   });
+    // }
+  };
+
   useEffect(() => {
     if (source === 'upper') {
-      onChange(propertiesKey, undefined, source);
-      if (props.parallel === false) {
-        // 串行
-        const params = props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
-        const data = props.form.getFieldsValue();
-        queryBuiltInParams(data, params).then((res: any) => {
-          if (res.status === 200) {
-            const actionParams = res.result.filter(
-              (item: any) => item.id === `action_${props.name}`,
-            );
-            const _data = props.name === 0 ? res.result : handleTreeData(actionParams);
-            setBuiltInList(_data);
-          }
-        });
-      } else {
-        // 并行
-        queryBuiltInParams({
-          trigger: { type: props.type },
-        }).then((res: any) => {
-          if (res.status === 200) {
-            setBuiltInList(handleTreeData(res.result));
-          }
-        });
-      }
+      sourceChangeEvent();
     }
   }, [source, props.type, props.parallel]);
 
   useEffect(() => {
-    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].value);
-          setSource(props.value[key].source);
-          const propertiesItem = props.properties.find((item: any) => item.id === key);
-          if (propertiesItem) {
-            setPropertiesType(propertiesItem.valueType.type);
-          }
-        });
+    if (props.trigger?.trigger?.device?.productId && source === 'upper') {
+      sourceChangeEvent();
+    }
+  }, [props.trigger?.trigger?.device?.productId, source]);
+
+  useEffect(() => {
+    if (props.productId) {
+      setPropertiesKey(undefined);
+      onChange('undefined', undefined, source);
+    }
+  }, [props.productId]);
+
+  useEffect(() => {
+    if (props.value) {
+      if (props.properties && props.properties.length) {
+        if (0 in props.value) {
+          setPropertiesValue(props.value[0]);
+        } else if ('undefined' in props.value) {
+          setPropertiesKey(undefined);
+          setPropertiesValue(undefined);
+        } else {
+          Object.keys(props.value).forEach((key: string) => {
+            setPropertiesKey(key);
+            setPropertiesValue(props.value[key].value);
+            setSource(props.value[key].source);
+            const propertiesItem = props.properties.find((item: any) => item.id === key);
+            if (propertiesItem) {
+              setPropertiesType(propertiesItem.valueType.type);
+            }
+          });
+        }
       }
+    } else {
+      setPropertiesKey(undefined);
+      setPropertiesValue(undefined);
     }
   }, [props.value, props.properties]);
 

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

@@ -17,6 +17,7 @@ interface FunctionCallProps {
   value?: any;
   onChange?: (data: any) => void;
   name?: string;
+  productId?: string;
 }
 
 export default (props: FunctionCallProps) => {
@@ -44,9 +45,19 @@ export default (props: FunctionCallProps) => {
           return item;
         }),
       });
+    } else {
+      formRef.current?.setFieldsValue({
+        table: [],
+      });
     }
   }, [props.value, props.functionData]);
 
+  useEffect(() => {
+    if (props.productId && props.onChange) {
+      props.onChange([]);
+    }
+  }, [props.productId]);
+
   const getItemNode = (record: any) => {
     const type = record.type;
     const name = record.name;

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

@@ -17,6 +17,7 @@ interface DeviceProps {
   onMessageTypeChange: (type: string) => void;
   onFunctionChange: (functionItem: any) => void;
   parallel?: boolean;
+  onProductIdChange: (id: string) => void;
 }
 
 enum SourceEnum {
@@ -87,12 +88,20 @@ export default (props: DeviceProps) => {
     } else {
       setSourceList(DefaultSourceOptions);
     }
-    props.form?.setFields([
-      {
-        name: ['actions', name, 'device', 'selector'],
-        value: SourceEnum.fixed,
-      },
-    ]);
+    const actions = props.form?.getFieldValue('actions');
+
+    if (actions[name] && actions[name].device) {
+      if (actions[name].device.selector) {
+        actions[name].device.selector = SourceEnum.fixed;
+      }
+      if (actions[name].device.selectorValues) {
+        actions[name].device.selectorValues = undefined;
+      }
+    }
+    props.form?.setFieldsValue({
+      actions,
+    });
+    setSelector(SourceEnum.fixed);
   }, [props.triggerType]);
 
   useEffect(() => {
@@ -102,6 +111,8 @@ export default (props: DeviceProps) => {
         handleMetadata(productItem.metadata);
       }
     }
+
+    props.onProductIdChange(productId);
   }, [productId]);
 
   useEffect(() => {
@@ -134,7 +145,6 @@ export default (props: DeviceProps) => {
   }, []);
 
   useEffect(() => {
-    console.log('actions-device', props.value);
     const deviceData = props.value;
     if (deviceData) {
       setProductId(deviceData.productId);

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

@@ -52,7 +52,7 @@ export default () => {
   const history = useHistory();
 
   const { getOtherPermission } = PermissionButton.usePermission('rule-engine/Scene');
-  const [triggerType, setTriggerType] = useState('');
+  const [triggerType, setTriggerType] = useState('device');
 
   const [loading, setLoading] = useState(false);
   const [parallel, setParallel] = useState(true); // 是否并行
@@ -62,6 +62,7 @@ export default () => {
   const [triggerValue, setTriggerValue] = useState<any>([]);
   const [triggerDatas, setTriggerDatas] = useState<any>({});
   const [actionParams, setActionParams] = useState<any>(undefined);
+  const [actionDataCount, setActionDataCount] = useState(0);
 
   const [actionsData, setActionsData] = useState<any[]>([]);
   const [isEdit, setIsEdit] = useState(false);
@@ -224,9 +225,6 @@ export default () => {
               layout={'vertical'}
               preserve={false}
               className={'scene-save'}
-              initialValues={{
-                actions: [undefined],
-              }}
               onValuesChange={(changeValue, allValues) => {
                 if (changeValue.trigger) {
                   if (changeValue.trigger.device) {
@@ -258,7 +256,7 @@ export default () => {
                     setActionParams({ trigger: allValues.trigger }); // 用于内置参数请求
                   }
                 }
-
+                console.log('scene save', allValues);
                 if (allValues.actions) {
                   setActionsData(allValues.actions);
                 }
@@ -288,6 +286,7 @@ export default () => {
                 <Form.Item
                   name={['trigger', 'type']}
                   rules={[{ required: true, message: '请选择触发方式' }]}
+                  initialValue={'device'}
                 >
                   <TriggerWay onSelect={setTriggerType} disabled={isEdit} />
                 </Form.Item>
@@ -357,7 +356,10 @@ export default () => {
                             name={name}
                             trigger={actionParams}
                             triggerType={triggerType}
-                            onRemove={() => remove(name)}
+                            onRemove={() => {
+                              remove(name);
+                              setActionDataCount(actionDataCount - 1);
+                            }}
                             onMove={(type) => {
                               if (type === 'up') {
                                 move(name, name - 1);
@@ -365,13 +367,21 @@ export default () => {
                                 move(name, name + 1);
                               }
                             }}
-                            actionItemData={actionsData.length && actionsData[name]}
-                            isLast={!actionsData.length || actionsData.length - 1 === name}
+                            actionItemData={actionDataCount && actionsData[name]}
+                            isLast={!actionDataCount || actionDataCount - 1 === name}
                             parallel={parallel}
                           />
                         ))}
                         <Form.Item noStyle>
-                          <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
+                          <Button
+                            type="dashed"
+                            onClick={() => {
+                              add();
+                              setActionDataCount(actionDataCount + 1);
+                            }}
+                            block
+                            icon={<PlusOutlined />}
+                          >
                             新增
                           </Button>
                         </Form.Item>
@@ -409,7 +419,7 @@ export default () => {
             </Form>
           </Col>
           <Col span={8}>
-            <Explanation />
+            <Explanation type={triggerType} />
           </Col>
         </Row>
       </Card>

+ 1 - 1
src/pages/system/DataSource/Management/EditTable.tsx

@@ -147,7 +147,7 @@ const EditTable = (props: Props) => {
               'x-component': 'ArrayTable.Column',
               'x-component-props': { title: '精度' },
               properties: {
-                scale: {
+                precision: {
                   type: 'number',
                   'x-decorator': 'FormItem',
                   'x-component': 'NumberPicker',

+ 5 - 0
yarn.lock

@@ -8639,6 +8639,11 @@ draftjs-utils@^0.9.4:
   resolved "https://registry.yarnpkg.com/draftjs-utils/-/draftjs-utils-0.9.4.tgz#976c61aa133dbbbfedd65ae1dd6627d7b98c6f08"
   integrity sha512-KYjABSbGpJrwrwmxVj5UhfV37MF/p0QRxKIyL+/+QOaJ8J9z1FBKxkblThbpR0nJi9lxPQWGg+gh+v0dAsSCCg==
 
+driver.js@^0.9.8:
+  version "0.9.8"
+  resolved "https://registry.npmmirror.com/driver.js/-/driver.js-0.9.8.tgz#4b327f4537b1c9b9fb19419de86174be821ae32a"
+  integrity sha512-bczjyKdX6XmFyCDkwtRmlaORDwfBk1xXmRO0CAe5VwNQTM98aWaG2LAIiIdTe53iV/B7W5lXlIy2xYtf0JRb7Q==
+
 dumi-assets-types@1.0.0, dumi-assets-types@^1.0.0-beta.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/dumi-assets-types/-/dumi-assets-types-1.0.0.tgz#d5368cb11045b203bf1ef1080e553b2287a2ec81"