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

+ 37 - 10
src/pages/link/DataCollect/Dashboard/index.tsx

@@ -83,17 +83,44 @@ const DeviceBoard = () => {
       setErrorPoint(errorPointRes.result);
     }
   };
-  const getInterval = (type: string) => {
-    switch (type) {
-      case 'year':
-        return '30d';
-      case 'month':
+
+  const getParams = (dt: any) => {
+    switch (dt.type) {
+      case 'today':
+        return {
+          limit: 24,
+          interval: '1h',
+        };
       case 'week':
-        return '1d';
+        return {
+          limit: 7,
+          interval: '1d',
+        };
       case 'hour':
-        return '1m';
+        return {
+          limit: 60,
+          interval: '1m',
+        };
       default:
-        return '1h';
+        const time = dt.end - dt.start;
+        const hour = 60 * 60 * 1000;
+        const days = hour * 24;
+        if (time <= hour) {
+          return {
+            limit: Math.abs(Math.ceil(time / (60 * 60))),
+            interval: '1m',
+          };
+        } else if (time > hour && time <= days) {
+          return {
+            limit: Math.abs(Math.ceil(time / hour)),
+            interval: '1h',
+          };
+        } else {
+          return {
+            limit: Math.abs(Math.ceil(dt / days)) + 1,
+            interval: '1d',
+          };
+        }
     }
   };
 
@@ -106,10 +133,10 @@ const DeviceBoard = () => {
         measurement: 'quantity',
         dimension: 'agg',
         params: {
-          limit: 15,
+          limit: getParams(data.time).limit,
           from: data.time.start,
           to: data.time.end,
-          interval: getInterval(data.time.type),
+          interval: getParams(data.time).interval,
           format: 'HH:mm',
         },
       },

+ 72 - 4
src/pages/link/DataCollect/components/Channel/Save/index.tsx

@@ -36,14 +36,16 @@ export default (props: Props) => {
 
   const getSecurityPolicyList = () => service.querySecurityPolicyList({});
   const getAuthTypeList = () => service.queryAuthTypeList({});
+  const getSecurityModesList = () => service.querySecurityModesList({});
+  const getCertificateList = () => service.queryCertificateList({});
 
   const useAsyncDataSource = (services: (arg0: Field) => Promise<any>) => (field: Field) => {
     field.loading = true;
     services(field).then(
       action.bound!((resp: any) => {
         field.dataSource = (resp?.result || []).map((item: any) => ({
-          label: item?.text || item,
-          value: item?.value || item,
+          label: item?.text || item?.name || item,
+          value: item?.value || item?.id || item,
         }));
         field.loading = false;
       }),
@@ -71,7 +73,7 @@ export default (props: Props) => {
       if (!(testIP(val) || testIPv6(val) || testDomain(val))) {
         return {
           type: 'error',
-          message: '请输入正确格式的IP地址',
+          message: '请输入正确格式的Modbus主机IP地址',
         };
       } else {
         return true;
@@ -212,6 +214,10 @@ export default (props: Props) => {
                 required: true,
                 message: '请输入端点url',
               },
+              // {
+              //   format: 'url',
+              //   message: '请输入正确的端点url',
+              // },
             ],
             'x-reactions': {
               dependencies: ['..provider'],
@@ -250,6 +256,62 @@ export default (props: Props) => {
               },
             ],
           },
+          'configuration.securityMode': {
+            title: '安全模式',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请选择安全模式',
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择安全模式',
+              },
+            ],
+            'x-reactions': [
+              '{{useAsyncDataSource(getSecurityModesList)}}',
+              {
+                dependencies: ['..provider'],
+                fulfill: {
+                  state: {
+                    visible: '{{$deps[0]==="OPC_UA"}}',
+                  },
+                },
+              },
+            ],
+          },
+          'configuration.certificate': {
+            title: '证书',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请选择证书',
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择证书',
+              },
+            ],
+            'x-reactions': [
+              '{{useAsyncDataSource(getCertificateList)}}',
+              {
+                dependencies: ['.securityMode'],
+                fulfill: {
+                  state: {
+                    visible: '{{$deps[0]==="SingAndEncrypt" || $deps[0]==="Sign"}}',
+                  },
+                },
+              },
+            ],
+          },
           'configuration.authType': {
             title: '权限认证',
             'x-component': 'Select',
@@ -394,7 +456,13 @@ export default (props: Props) => {
       <Form form={form} layout="vertical">
         <SchemaField
           schema={schema}
-          scope={{ useAsyncDataSource, getSecurityPolicyList, getAuthTypeList }}
+          scope={{
+            useAsyncDataSource,
+            getSecurityPolicyList,
+            getAuthTypeList,
+            getSecurityModesList,
+            getCertificateList,
+          }}
         />
       </Form>
     </Modal>

+ 4 - 0
src/pages/link/DataCollect/components/Channel/index.tsx

@@ -73,6 +73,10 @@ export default observer((props: Props) => {
         },
       },
     },
+    {
+      title: '说明',
+      dataIndex: 'description',
+    },
   ];
 
   const handleSearch = (params: any) => {

+ 29 - 9
src/pages/link/DataCollect/components/Device/Save/index.tsx

@@ -1,7 +1,7 @@
 import { Button, Modal } from 'antd';
-import { createForm } from '@formily/core';
+import { createForm, Field, onFieldReact } from '@formily/core';
 import { createSchemaField } from '@formily/react';
-import React, { useEffect, useState } from 'react';
+import React from 'react';
 import * as ICONS from '@ant-design/icons';
 import { Form, FormGrid, FormItem, Input, Select, NumberPicker, Password } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
@@ -17,15 +17,35 @@ interface Props {
 }
 
 export default (props: Props) => {
-  const [data, setData] = useState<Partial<ChannelItem>>(props.data);
-
-  useEffect(() => {
-    setData(props.data);
-  }, [props.data]);
-
   const form = createForm({
     validateFirst: true,
-    initialValues: data || {},
+    initialValues: Object.keys(props.data).length
+      ? props.data
+      : {
+          circuitBreaker: {
+            type: 'LowerFrequency',
+          },
+        },
+    effects: () => {
+      onFieldReact('circuitBreaker.type', async (field, f) => {
+        const func = (field as Field).value;
+        f.setFieldState('circuitBreaker.type', (state) => {
+          let tooltip = '';
+          if (func === 'LowerFrequency') {
+            tooltip =
+              '连续20次异常,降低连接频率至原有频率的1/10(重试间隔不超过1分钟),故障处理后自动恢复至设定连接频率';
+          } else if (func === 'Break') {
+            tooltip = '连续10分钟异常,停止采集数据进入熔断状态,设备重新启用后恢复采集状态';
+          } else if (func === 'Ignore') {
+            tooltip = '忽略异常,保持原采集频率超时时间为5s';
+          }
+          state.decoratorProps = {
+            tooltip: tooltip,
+            gridSpan: 2,
+          };
+        });
+      });
+    },
   });
 
   const SchemaField = createSchemaField({

+ 101 - 11
src/pages/link/DataCollect/components/Point/Save/modbus.tsx

@@ -1,5 +1,5 @@
 import { Button, Modal } from 'antd';
-import { createForm } from '@formily/core';
+import { createForm, Field, registerValidateRules } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import React, { useEffect, useState } from 'react';
 import * as ICONS from '@ant-design/icons';
@@ -16,6 +16,7 @@ import {
 import type { ISchema } from '@formily/json-schema';
 import service from '@/pages/link/DataCollect/service';
 import { onlyMessage } from '@/utils/util';
+import { action } from '@formily/reactive';
 
 interface Props {
   data: Partial<PointItem>;
@@ -25,9 +26,13 @@ interface Props {
 
 export default (props: Props) => {
   const [data, setData] = useState<Partial<PointItem>>(props.data);
-
   useEffect(() => {
-    setData(props.data);
+    setData({
+      ...props.data,
+      accessModes: props.data?.accessModes
+        ? (props.data?.accessModes || []).map((item) => item.value)
+        : [],
+    });
   }, [props.data]);
 
   const form = createForm({
@@ -52,6 +57,39 @@ export default (props: Props) => {
     },
   });
 
+  const getCodecProvider = () => service.queryCodecProvider();
+
+  const useAsyncDataSource = (services: (arg0: Field) => Promise<any>) => (field: Field) => {
+    field.loading = true;
+    services(field).then(
+      action.bound!((resp: any) => {
+        field.dataSource = (resp?.result || []).map((item: any) => ({
+          label: item.name,
+          value: item.id,
+        }));
+        field.loading = false;
+      }),
+    );
+  };
+
+  registerValidateRules({
+    checkLength(value) {
+      if (String(value).length > 64) {
+        return {
+          type: 'error',
+          message: '最多可输入64个字符',
+        };
+      }
+      if (!(value % 1 === 0)) {
+        return {
+          type: 'error',
+          message: '请输入非0正整数',
+        };
+      }
+      return '';
+    },
+  });
+
   const schema: ISchema = {
     type: 'object',
     properties: {
@@ -124,11 +162,14 @@ export default (props: Props) => {
               },
               {
                 max: 255,
-                message: '请输入0-255之间的整数',
+                message: '请输入0-255之间的整数',
               },
               {
                 min: 0,
-                message: '请输入0-255之间的整整数',
+                message: '请输入0-255之间的正整数',
+              },
+              {
+                checkLength: true,
               },
             ],
           },
@@ -147,6 +188,13 @@ export default (props: Props) => {
                 required: true,
                 message: '请输入起始位置',
               },
+              {
+                min: 0,
+                message: '请输入非0正整数',
+              },
+              {
+                checkLength: true,
+              },
             ],
           },
           'configuration.parameter.quantity': {
@@ -164,6 +212,13 @@ export default (props: Props) => {
                 required: true,
                 message: '请输入寄存器数量',
               },
+              {
+                min: 0,
+                message: '请输入非0正整数',
+              },
+              {
+                checkLength: true,
+              },
             ],
           },
           'configuration.codec.provider': {
@@ -176,7 +231,7 @@ export default (props: Props) => {
             'x-component-props': {
               placeholder: '请选择数据类型',
             },
-            enum: [],
+            'x-reactions': '{{useAsyncDataSource(getCodecProvider)}}',
             'x-validator': [
               {
                 required: true,
@@ -195,7 +250,6 @@ export default (props: Props) => {
             'x-component-props': {
               placeholder: '请输入缩放因子',
             },
-            enum: [],
             'x-validator': [
               {
                 required: true,
@@ -205,6 +259,7 @@ export default (props: Props) => {
           },
           accessModes: {
             title: '访问类型',
+            type: 'array',
             'x-component': 'Select',
             'x-decorator': 'FormItem',
             'x-decorator-props': {
@@ -212,6 +267,7 @@ export default (props: Props) => {
             },
             'x-component-props': {
               placeholder: '请选择访问类型',
+              mode: 'multiple',
             },
             enum: [
               { label: '读', value: 'read' },
@@ -232,10 +288,18 @@ export default (props: Props) => {
             'x-decorator-props': {
               gridSpan: 2,
             },
-            default: 3,
+            default: 3000,
+            'x-reactions': {
+              dependencies: ['..accessModes'],
+              fulfill: {
+                state: {
+                  visible: '{{($deps[0] || []).includes("subscribe")}}',
+                },
+              },
+            },
             'x-component-props': {
               placeholder: '请输入采集频率',
-              addonAfter: '秒',
+              addonAfter: '秒',
               style: {
                 width: '100%',
               },
@@ -245,16 +309,27 @@ export default (props: Props) => {
                 required: true,
                 message: '请输入采集频率',
               },
+              {
+                checkLength: true,
+              },
             ],
           },
           features: {
-            title: '采集特性',
+            title: '',
             type: 'array',
             'x-component': 'Checkbox.Group',
             'x-decorator': 'FormItem',
             'x-decorator-props': {
               gridSpan: 2,
             },
+            'x-reactions': {
+              dependencies: ['.accessModes'],
+              fulfill: {
+                state: {
+                  visible: '{{($deps[0] || []).includes("subscribe")}}',
+                },
+              },
+            },
             enum: [
               {
                 label: '只推送变化的数据',
@@ -262,6 +337,20 @@ export default (props: Props) => {
               },
             ],
           },
+          description: {
+            title: '说明',
+            'x-component': 'Input.TextArea',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              rows: 3,
+              showCount: true,
+              maxLength: 200,
+              placeholder: '请输入说明',
+            },
+          },
         },
       },
     },
@@ -269,6 +358,7 @@ export default (props: Props) => {
 
   const save = async () => {
     const value = await form.submit<PointItem>();
+    console.log(value);
     const response: any = props.data?.id
       ? await service.updatePoint(props.data?.id, { ...props.data, ...value })
       : await service.savePoint({ ...props.data, ...value });
@@ -301,7 +391,7 @@ export default (props: Props) => {
       ]}
     >
       <Form form={form} layout="vertical">
-        <SchemaField schema={schema} />
+        <SchemaField schema={schema} scope={{ useAsyncDataSource, getCodecProvider }} />
       </Form>
     </Modal>
   );

+ 64 - 5
src/pages/link/DataCollect/components/Point/Save/opc-ua.tsx

@@ -1,5 +1,5 @@
 import { Button, Modal } from 'antd';
-import { createForm, Field } from '@formily/core';
+import { createForm, Field, registerValidateRules } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import React, { useEffect, useState } from 'react';
 import * as ICONS from '@ant-design/icons';
@@ -28,7 +28,12 @@ export default (props: Props) => {
   const [data, setData] = useState<Partial<PointItem>>(props.data);
 
   useEffect(() => {
-    setData(props.data);
+    setData({
+      ...props.data,
+      accessModes: props.data?.accessModes
+        ? (props.data?.accessModes || []).map((item) => item.value)
+        : [],
+    });
   }, [props.data]);
 
   const form = createForm({
@@ -68,6 +73,24 @@ export default (props: Props) => {
     );
   };
 
+  registerValidateRules({
+    checkLength(value) {
+      if (String(value).length > 64) {
+        return {
+          type: 'error',
+          message: '最多可输入64个字符',
+        };
+      }
+      if (!(value % 1 === 0)) {
+        return {
+          type: 'error',
+          message: '请输入非0正整数',
+        };
+      }
+      return '';
+    },
+  });
+
   const schema: ISchema = {
     type: 'object',
     properties: {
@@ -121,6 +144,7 @@ export default (props: Props) => {
           },
           accessModes: {
             title: '访问类型',
+            type: 'array',
             'x-component': 'Select',
             'x-decorator': 'FormItem',
             'x-decorator-props': {
@@ -128,6 +152,7 @@ export default (props: Props) => {
             },
             'x-component-props': {
               placeholder: '请选择访问类型',
+              mode: 'multiple',
             },
             enum: [
               { label: '读', value: 'read' },
@@ -148,10 +173,18 @@ export default (props: Props) => {
             'x-decorator-props': {
               gridSpan: 2,
             },
-            default: 3,
+            default: 3000,
+            'x-reactions': {
+              dependencies: ['.accessModes'],
+              fulfill: {
+                state: {
+                  visible: '{{($deps[0] || []).includes("subscribe")}}',
+                },
+              },
+            },
             'x-component-props': {
               placeholder: '请输入采集频率',
-              addonAfter: '秒',
+              addonAfter: '秒',
               style: {
                 width: '100%',
               },
@@ -161,16 +194,27 @@ export default (props: Props) => {
                 required: true,
                 message: '请输入采集频率',
               },
+              {
+                checkLength: true,
+              },
             ],
           },
           features: {
-            title: '采集特性',
+            title: '',
             type: 'array',
             'x-component': 'Checkbox.Group',
             'x-decorator': 'FormItem',
             'x-decorator-props': {
               gridSpan: 2,
             },
+            'x-reactions': {
+              dependencies: ['.accessModes'],
+              fulfill: {
+                state: {
+                  visible: '{{($deps[0] || []).includes("subscribe")}}',
+                },
+              },
+            },
             enum: [
               {
                 label: '只推送变化的数据',
@@ -178,6 +222,20 @@ export default (props: Props) => {
               },
             ],
           },
+          description: {
+            title: '说明',
+            'x-component': 'Input.TextArea',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              rows: 3,
+              showCount: true,
+              maxLength: 200,
+              placeholder: '请输入说明',
+            },
+          },
         },
       },
     },
@@ -185,6 +243,7 @@ export default (props: Props) => {
 
   const saveData = async () => {
     const value = await form.submit<PointItem>();
+    console.log(value);
     const response: any = props.data?.id
       ? await service.updatePoint(props.data?.id, { ...props.data, ...value })
       : await service.savePoint({ ...props.data, ...value });

+ 1 - 1
src/pages/link/DataCollect/components/Point/index.tsx

@@ -91,7 +91,7 @@ export default observer((props: Props) => {
       ?.pipe(map((res) => res.payload))
       .subscribe((payload: any) => {
         propertyValue[payload?.pointId] = { ...payload };
-        setPropertyValue([...propertyValue]);
+        setPropertyValue({ ...propertyValue });
       });
   };
   const handleSearch = (params: any) => {

+ 5 - 2
src/pages/link/DataCollect/components/Tree/index.tsx

@@ -1,5 +1,5 @@
 import { DownOutlined, PlusOutlined, FormOutlined, DeleteOutlined } from '@ant-design/icons';
-import { Button, Input, Tree, Space, Popconfirm } from 'antd';
+import { Button, Input, Tree, Space, Popconfirm, Badge } from 'antd';
 import { observer } from '@formily/react';
 import { model } from '@formily/reactive';
 import { Empty } from '@/components';
@@ -102,7 +102,10 @@ export default observer((props: Props) => {
                         }}
                       >
                         <img width={'20px'} style={{ marginRight: 5 }} src={channelImg} />
-                        <div className={'ellipsis'}>{item.name}</div>
+                        <div className={'ellipsis'}>
+                          <Badge status={item.state?.value === 'enabled' ? 'success' : 'error'} />
+                          {item.name}
+                        </div>
                       </div>
                       <div>
                         <Space className={styles.iconColor}>

+ 12 - 0
src/pages/link/DataCollect/service.ts

@@ -110,6 +110,18 @@ class Service {
       params,
     });
 
+  public querySecurityModesList = (params?: any) =>
+    request(`/${SystemConst.API_BASE}/data-collect/opc/security-modes`, {
+      method: 'GET',
+      params,
+    });
+
+  public queryCertificateList = (params?: any) =>
+    request(`/${SystemConst.API_BASE}/network/certificate/_query/no-paging?paging=false`, {
+      method: 'GET',
+      params,
+    });
+
   public queryAuthTypeList = (params?: any) =>
     request(`/${SystemConst.API_BASE}/data-collect/opc/auth-types`, {
       method: 'GET',