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

Merge branch 'next' into next-xyh

# Conflicts:
#	src/pages/user/Login/index.tsx
xieyonghong 3 лет назад
Родитель
Сommit
f2c312c771
47 измененных файлов с 5058 добавлено и 3266 удалено
  1. 2 2
      config/proxy.ts
  2. 13 8
      src/components/FIndicators/index.tsx
  3. 15 4
      src/components/FUpload/index.tsx
  4. 16 3
      src/pages/account/Center/index.tsx
  5. 29 0
      src/pages/device/Firmware/Save/RemoveData.tsx
  6. 37 10
      src/pages/device/Firmware/Save/index.tsx
  7. 0 1
      src/pages/device/Firmware/Task/Detail/index.tsx
  8. 0 2
      src/pages/device/Firmware/Task/index.tsx
  9. 0 3
      src/pages/device/Firmware/index.tsx
  10. 10 2
      src/pages/device/Firmware/service.ts
  11. 0 303
      src/pages/device/Instance/Detail/Modbus/channelList.tsx
  12. 0 70
      src/pages/device/Instance/Detail/Modbus/editTable.tsx
  13. 1 0
      src/pages/device/Instance/Detail/Modbus/index.less
  14. 508 382
      src/pages/device/Instance/Detail/Modbus/index.tsx
  15. 1 3
      src/pages/device/Instance/Detail/index.tsx
  16. 1 1
      src/pages/device/Instance/index.tsx
  17. 30 36
      src/pages/device/Product/Detail/index.tsx
  18. 18 13
      src/pages/home/index.tsx
  19. 76 0
      src/pages/home/init/accountInit.tsx
  20. 12 12
      src/pages/init-home/components/basis.tsx
  21. 2453 1530
      src/pages/init-home/components/data/RoleData.ts
  22. 8 8
      src/pages/init-home/components/data/index.tsx
  23. 42 39
      src/pages/init-home/components/menu.tsx
  24. 76 67
      src/pages/init-home/components/role.tsx
  25. 38 30
      src/pages/init-home/index.tsx
  26. 13 9
      src/pages/init-home/service.ts
  27. 121 0
      src/pages/link/Channel/Modbus/Export/index.tsx
  28. 225 0
      src/pages/link/Channel/Modbus/import/index.tsx
  29. 276 101
      src/pages/link/Channel/Modbus/index.tsx
  30. 16 2
      src/pages/link/Channel/Modbus/saveChannel.tsx
  31. 32 12
      src/pages/link/Channel/Modbus/savePoint.tsx
  32. 63 30
      src/pages/link/Channel/Modbus/service.ts
  33. 387 157
      src/pages/link/Channel/Opcua/index.tsx
  34. 250 0
      src/pages/link/Channel/Opcua/saveChannel.tsx
  35. 177 0
      src/pages/link/Channel/Opcua/savePoint.tsx
  36. 10 0
      src/pages/link/Channel/Opcua/service.ts
  37. 10 7
      src/pages/link/Channel/channelCard.tsx
  38. 9 3
      src/pages/link/Channel/index.less
  39. 0 372
      src/pages/link/Channel/new.tsx
  40. 16 11
      src/pages/media/Cascade/Channel/index.tsx
  41. 1 1
      src/pages/media/Cascade/Save/index.tsx
  42. 13 9
      src/pages/notice/Config/Debug/index.tsx
  43. 3 3
      src/pages/notice/Config/Detail/index.tsx
  44. 38 12
      src/pages/system/Menu/Setting/baseMenu.ts
  45. 1 1
      src/pages/system/Platforms/save.tsx
  46. 10 6
      src/pages/user/Login/index.tsx
  47. 1 1
      src/pages/user/Login/service.ts

+ 2 - 2
config/proxy.ts

@@ -9,8 +9,8 @@
 export default {
   dev: {
     '/api': {
-      // target: 'http://192.168.32.8:8844/',
-      // ws: 'ws://192.168.32.8:8844/',
+      // target: 'http://192.168.32.28:8844/',
+      // ws: 'ws://192.168.32.28:8844/',
       // 开发环境
       // target: 'http://120.79.18.123:8844/',
       // ws: 'ws://120.79.18.123:8844/',

+ 13 - 8
src/components/FIndicators/index.tsx

@@ -18,13 +18,17 @@ const FIndicators = (props: Props) => {
           <InputNumber
             value={value?.value ? value?.value[0] : ''}
             onChange={(val) => {
-              onChange({
-                ...value,
-                value: [
-                  value?.range && val < value?.value[1] ? val : value?.value[0],
-                  value?.value[1],
-                ],
-              });
+              if (value?.range) {
+                onChange({
+                  ...value,
+                  value: [val > value?.value[1] ? value?.value[0] : val, value?.value[1] || ''],
+                });
+              } else {
+                onChange({
+                  ...value,
+                  value: [val],
+                });
+              }
             }}
           />
           {value.range && (
@@ -35,7 +39,7 @@ const FIndicators = (props: Props) => {
                 onChange={(val) => {
                   onChange({
                     ...value,
-                    value: [value?.value && value?.value[0], val],
+                    value: [value?.value[0], val > value?.value[0] ? val : value?.value[1]],
                   });
                 }}
               />
@@ -117,6 +121,7 @@ const FIndicators = (props: Props) => {
         onChange={(e) => {
           onChange({
             ...value,
+            value: e.target.checked ? [undefined, undefined] : [undefined],
             range: e.target.checked,
           });
         }}

+ 15 - 4
src/components/FUpload/index.tsx

@@ -1,4 +1,4 @@
-import { UploadOutlined } from '@ant-design/icons';
+import { DeleteOutlined, UploadOutlined } from '@ant-design/icons';
 import SystemConst from '@/utils/const';
 import Token from '@/utils/token';
 import { useState } from 'react';
@@ -6,7 +6,7 @@ import { connect } from '@formily/react';
 import { Input, Upload } from 'antd';
 import type { UploadChangeParam } from 'antd/lib/upload/interface';
 import './index.less';
-
+import { service } from '@/pages/device/Firmware';
 interface Props {
   value: any;
   onChange: (value: any) => void;
@@ -17,12 +17,13 @@ interface Props {
 const FUpload = connect((props: Props) => {
   const [url, setUrl] = useState<any>(props?.value?.url);
 
-  const handleChange = (info: UploadChangeParam) => {
+  const handleChange = async (info: UploadChangeParam) => {
     if (info.file.status === 'done') {
       const result = info.file.response?.result;
+      const api = await service.querySystemApi();
       const f = {
         ...result,
-        url: `${location.protocol}//${SystemConst.API_BASE}/file/${result?.id}?accessKey=${result?.others?.accessKey}`,
+        url: `${api?.result?.basePath}file/${result?.id}?accessKey=${result?.others?.accessKey}`,
       };
       setUrl(f.url);
       props.onChange(f);
@@ -39,6 +40,16 @@ const FUpload = connect((props: Props) => {
       multiple={false}
       onChange={handleChange}
       progress={{}}
+      showUploadList={{
+        removeIcon: (
+          <DeleteOutlined
+            onClick={() => {
+              setUrl('');
+              props.onChange(undefined);
+            }}
+          />
+        ),
+      }}
     >
       <Input
         placeholder={props.placeholder}

+ 16 - 3
src/pages/account/Center/index.tsx

@@ -23,8 +23,9 @@ import Service from '@/pages/account/Center/service';
 import moment from 'moment';
 import { useModel } from 'umi';
 import usePermissions from '@/hooks/permission';
-import { PermissionButton } from '@/components';
+import { Ellipsis, PermissionButton } from '@/components';
 import { onlyMessage } from '@/utils/util';
+import AccountInit from '@/pages/home/init/accountInit';
 
 export const service = new Service();
 
@@ -129,7 +130,6 @@ const Center = () => {
   }, []);
 
   useEffect(() => {
-    console.log(data);
     if (data?.name) {
       const item = {
         ...initialState?.currentUser?.user,
@@ -168,7 +168,9 @@ const Center = () => {
           <div className={styles.content}>
             <Descriptions column={4} layout="vertical" labelStyle={{ fontWeight: 600 }}>
               <Descriptions.Item label="登录账号">{data?.username}</Descriptions.Item>
-              <Descriptions.Item label="账号ID">{data?.id}</Descriptions.Item>
+              <Descriptions.Item label="账号ID">
+                <Ellipsis title={data?.id} tooltip={{ placement: 'topLeft' }} maxWidth={'90%'} />
+              </Descriptions.Item>
               <Descriptions.Item label="注册时间">
                 {moment(data?.createTime).format('YYYY-MM-DD HH:mm:ss')}
               </Descriptions.Item>
@@ -310,6 +312,17 @@ const Center = () => {
           ))}
         </Row>
       </Card>
+      <Card
+        style={{ marginTop: 15 }}
+        title={
+          <div style={{ fontSize: '22px' }}>
+            <Divider type="vertical" style={{ backgroundColor: '#2F54EB', width: 3 }} />
+            首页视图
+          </div>
+        }
+      >
+        <AccountInit />,
+      </Card>
       {infos && (
         <InfoEdit
           data={data}

+ 29 - 0
src/pages/device/Firmware/Save/RemoveData.tsx

@@ -0,0 +1,29 @@
+import { DeleteOutlined } from '@ant-design/icons';
+import { ArrayItems } from '@formily/antd';
+import { Popconfirm } from 'antd';
+import { useField } from '@formily/react';
+
+const RemoveData = () => {
+  const index = ArrayItems.useIndex!();
+  const self = useField();
+  const array = ArrayItems.useArray!();
+  if (!array) return null;
+  if (array.field?.pattern !== 'editable') return null;
+
+  return (
+    <div>
+      <Popconfirm
+        title={'确认删除'}
+        onConfirm={() => {
+          if (self?.disabled) return;
+          array.field?.remove?.(index);
+          array.props?.onRemove?.(index);
+        }}
+      >
+        <DeleteOutlined />
+      </Popconfirm>
+    </div>
+  );
+};
+
+export default RemoveData;

+ 37 - 10
src/pages/device/Firmware/Save/index.tsx

@@ -1,7 +1,7 @@
 import { Modal } from 'antd';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import { createSchemaField } from '@formily/react';
-import { Form, FormGrid, FormItem, Input, Select, ArrayTable } from '@formily/antd';
+import { Form, FormGrid, FormItem, Input, Select, ArrayTable, NumberPicker } from '@formily/antd';
 import type { Field } from '@formily/core';
 import { onFieldValueChange, onFormInit } from '@formily/core';
 import { createForm } from '@formily/core';
@@ -12,6 +12,8 @@ import { service } from '@/pages/device/Firmware';
 import { useRef } from 'react';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import { onlyMessage } from '@/utils/util';
+import RemoveData from './RemoveData';
+import encodeQuery from '@/utils/encodeQuery';
 
 interface Props {
   data?: FirmwareItem;
@@ -55,7 +57,13 @@ const Save = (props: Props) => {
       }),
     );
   };
-  const loadData = async () => service.queryProduct();
+  const loadData = async () =>
+    service.queryProduct(
+      encodeQuery({
+        terms: { state: 1 },
+        sorts: { createTime: 'desc' },
+      }),
+    );
   const SchemaField = createSchemaField({
     components: {
       FormItem,
@@ -64,6 +72,8 @@ const Save = (props: Props) => {
       FUpload,
       Select,
       ArrayTable,
+      NumberPicker,
+      RemoveData,
     },
   });
 
@@ -83,6 +93,7 @@ const Save = (props: Props) => {
       close();
     }
   };
+
   const schema: ISchema = {
     type: 'object',
     properties: {
@@ -121,9 +132,6 @@ const Save = (props: Props) => {
             'x-decorator': 'FormItem',
             'x-component': 'Select',
             'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
-            'x-component-props': {
-              placeholder: '请选择所属产品',
-            },
             'x-decorator-props': {
               gridSpan: 2,
             },
@@ -134,6 +142,13 @@ const Save = (props: Props) => {
                 message: '请选择所属产品',
               },
             ],
+            'x-component-props': {
+              placeholder: '请选择所属产品',
+              showSearch: true,
+              allowClear: true,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+            },
           },
           version: {
             title: '版本号',
@@ -160,7 +175,7 @@ const Save = (props: Props) => {
           versionOrder: {
             title: '版本序号',
             'x-decorator': 'FormItem',
-            'x-component': 'Input',
+            'x-component': 'NumberPicker',
             'x-component-props': {
               placeholder: '请输入版本序号',
             },
@@ -244,7 +259,7 @@ const Save = (props: Props) => {
             ],
           },
           upload: {
-            title: '件上传',
+            title: '件上传',
             'x-decorator': 'FormItem',
             'x-component': 'FUpload',
             'x-component-props': {
@@ -284,8 +299,14 @@ const Save = (props: Props) => {
                   properties: {
                     id: {
                       type: 'string',
-                      'x-decorator': 'Editable',
+                      'x-decorator': 'FormItem',
                       'x-component': 'Input',
+                      'x-validator': [
+                        {
+                          required: true,
+                          message: '请输入KEY',
+                        },
+                      ],
                     },
                   },
                 },
@@ -298,6 +319,12 @@ const Save = (props: Props) => {
                       type: 'string',
                       'x-decorator': 'FormItem',
                       'x-component': 'Input',
+                      'x-validator': [
+                        {
+                          required: true,
+                          message: '请输入VALUE',
+                        },
+                      ],
                     },
                   },
                 },
@@ -315,7 +342,7 @@ const Save = (props: Props) => {
                       properties: {
                         remove: {
                           type: 'void',
-                          'x-component': 'ArrayTable.Remove',
+                          'x-component': 'RemoveData',
                         },
                       },
                     },
@@ -327,7 +354,7 @@ const Save = (props: Props) => {
               add: {
                 type: 'void',
                 'x-component': 'ArrayTable.Addition',
-                title: '添加条目',
+                title: '添加',
               },
             },
           },

+ 0 - 1
src/pages/device/Firmware/Task/Detail/index.tsx

@@ -229,7 +229,6 @@ const Detail = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       fixed: 'right',
       width: 200,
       render: (text: any, record: any) =>

+ 0 - 2
src/pages/device/Firmware/Task/index.tsx

@@ -101,7 +101,6 @@ const Task = observer(() => {
         defaultMessage: '说明',
       }),
       ellipsis: true,
-      align: 'center',
       dataIndex: 'description',
     },
     {
@@ -116,7 +115,6 @@ const Task = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       fixed: 'right',
       render: (text, record) => [

+ 0 - 3
src/pages/device/Firmware/index.tsx

@@ -92,7 +92,6 @@ const Firmware = observer(() => {
       }),
       dataIndex: 'createTime',
       width: '200px',
-      align: 'center',
       ellipsis: true,
       valueType: 'dateTime',
       // render: (text: any) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
@@ -105,7 +104,6 @@ const Firmware = observer(() => {
         defaultMessage: '说明',
       }),
       ellipsis: true,
-      align: 'center',
       dataIndex: 'description',
     },
     {
@@ -114,7 +112,6 @@ const Firmware = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       fixed: 'right',
       render: (text, record) => [

+ 10 - 2
src/pages/device/Firmware/service.ts

@@ -4,6 +4,11 @@ import SystemConst from '@/utils/const';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 
 class Service extends BaseService<FirmwareItem> {
+  querySystemApi = () =>
+    request(`/${SystemConst.API_BASE}/system/apis`, {
+      method: 'GET',
+    });
+
   task = (params: Record<string, unknown>) =>
     request(`/${SystemConst.API_BASE}/firmware/upgrade/task/detail/_query`, {
       method: 'POST',
@@ -50,8 +55,11 @@ class Service extends BaseService<FirmwareItem> {
       data: params,
     });
 
-  queryProduct = () =>
-    request(`/${SystemConst.API_BASE}/device/product/_query/no-paging?paging=false`);
+  queryProduct = (params?: any) =>
+    request(`/${SystemConst.API_BASE}/device/product/_query/no-paging?paging=false`, {
+      method: 'GET',
+      params,
+    });
 
   queryDevice = () =>
     request(`/${SystemConst.API_BASE}/device/instance/_query/no-paging?paging=false`);

+ 0 - 303
src/pages/device/Instance/Detail/Modbus/channelList.tsx

@@ -1,303 +0,0 @@
-import {
-  FormItem,
-  Input,
-  ArrayTable,
-  Editable,
-  FormButtonGroup,
-  Submit,
-  Select,
-  NumberPicker,
-} from '@formily/antd';
-import { createForm, Field, onFieldReact, FormPath } from '@formily/core';
-import { FormProvider, createSchemaField } from '@formily/react';
-import { Button, Badge } from 'antd';
-
-const Render = (props: any) => <>{props.value}</>;
-const ActionButton = () => {
-  const record = ArrayTable.useRecord?.();
-  const index = ArrayTable.useIndex?.();
-  return (
-    <Button
-      onClick={() => {
-        console.log(record(index));
-      }}
-    >
-      启用
-    </Button>
-  );
-};
-const StatusRender = (props: any) => {
-  switch (props.value?.value) {
-    case 'enabled':
-      return <Badge status="success" text={props.value?.text} />;
-    case 'disabled':
-      return <Badge status="error" text={props.value?.text} />;
-    case 'connect':
-      return <Badge status="success" text={props.value?.text} />;
-    case 'disconnect':
-      return <Badge status="warning" text={props.value?.text} />;
-    default:
-      return <></>;
-  }
-};
-
-const SchemaField = createSchemaField({
-  components: {
-    FormItem,
-    Editable,
-    Input,
-    ArrayTable,
-    Select,
-    NumberPicker,
-    Render,
-    ActionButton,
-    StatusRender,
-  },
-});
-
-const form = createForm({
-  initialValues: {
-    array: [
-      {
-        // a2: '111',
-        a1: 'wendu',
-        // a3: '1111',
-        id: '0718',
-        state: {
-          text: '正常',
-          value: 'enabled',
-        },
-      },
-      {
-        // a2: '2',
-        a1: 'sudu',
-        // a3: '3',
-        id: '0718-1',
-        state: {
-          text: '禁用',
-          value: 'disabled',
-        },
-      },
-    ],
-  },
-  effects: () => {
-    onFieldReact('array.*.a2', (field, f) => {
-      const value = (field as Field).value;
-      const path = FormPath.transform(field.path, /\d+/, (index) => `array.${parseInt(index)}.a3`);
-      console.log(value);
-      f.setFieldState(path, (state) => {
-        if (value) {
-          state.required = true;
-          form.validate();
-        } else {
-          state.required = false;
-        }
-      });
-    });
-  },
-});
-
-const schema = {
-  type: 'object',
-  properties: {
-    array: {
-      type: 'array',
-      'x-decorator': 'FormItem',
-      'x-component': 'ArrayTable',
-      'x-component-props': {
-        pagination: { pageSize: 10 },
-        scroll: { x: '100%' },
-      },
-      items: {
-        type: 'object',
-        properties: {
-          column1: {
-            type: 'void',
-            'x-component': 'ArrayTable.Column',
-            'x-component-props': { width: 120, title: '属性' },
-            properties: {
-              a1: {
-                type: 'string',
-                'x-component': 'Render',
-              },
-            },
-          },
-          column2: {
-            type: 'void',
-            'x-component': 'ArrayTable.Column',
-            'x-component-props': { width: 200, title: '通道' },
-            properties: {
-              a2: {
-                type: 'string',
-                'x-decorator': 'FormItem',
-                'x-component': 'Select',
-                'x-component-props': {
-                  placeholder: '请选择',
-                  showSearch: true,
-                  allowClear: true,
-                  showArrow: true,
-                  filterOption: (input: string, option: any) =>
-                    option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
-                  onBlur: () => {
-                    const value = form.validate();
-                    console.log(value.array?.value);
-                  },
-                },
-                enum: [
-                  {
-                    label: '通道1',
-                    value: 'channel1',
-                  },
-                  {
-                    label: '通道2',
-                    value: 'channel2',
-                  },
-                ],
-              },
-            },
-          },
-          column3: {
-            type: 'void',
-            'x-component': 'ArrayTable.Column',
-            'x-component-props': { width: 200, title: '数据点名称' },
-            properties: {
-              a3: {
-                type: 'string',
-                'x-decorator': 'FormItem',
-                'x-component': 'Select',
-                'x-validator': {
-                  triggerType: 'onBlur',
-                },
-                'x-component-props': {
-                  placeholder: '请选择',
-                  showSearch: true,
-                  allowClear: true,
-                  showArrow: true,
-                  filterOption: (input: string, option: any) =>
-                    option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
-                },
-                enum: [
-                  {
-                    label: '名称1',
-                    value: 'name1',
-                  },
-                  {
-                    label: '名称2',
-                    value: 'name2',
-                  },
-                ],
-              },
-            },
-          },
-          column4: {
-            type: 'void',
-            'x-component': 'ArrayTable.Column',
-            'x-component-props': { width: 200, title: '数据点类型' },
-            properties: {
-              a4: {
-                type: 'string',
-                'x-decorator': 'FormItem',
-                'x-component': 'Select',
-                'x-component-props': {
-                  placeholder: '请选择',
-                  showSearch: true,
-                  allowClear: true,
-                  showArrow: true,
-                  filterOption: (input: string, option: any) =>
-                    option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
-                },
-                enum: [
-                  {
-                    label: '类型1',
-                    value: 'type1',
-                  },
-                  {
-                    label: '类型2',
-                    value: 'type2',
-                  },
-                ],
-              },
-            },
-          },
-          column5: {
-            type: 'void',
-            'x-component': 'ArrayTable.Column',
-            'x-component-props': { width: 200, title: '缩放因子' },
-            properties: {
-              a5: {
-                type: 'string',
-                default: 1,
-                'x-decorator': 'FormItem',
-                'x-component': 'NumberPicker',
-                'x-component-props': {
-                  min: 1,
-                },
-              },
-            },
-          },
-          column6: {
-            type: 'void',
-            'x-component': 'ArrayTable.Column',
-            'x-component-props': { width: 200, title: '数据点说明' },
-            properties: {
-              a6: {
-                type: 'string',
-                'x-decorator': 'FormItem',
-                'x-component': 'Render',
-              },
-            },
-          },
-          column7: {
-            type: 'void',
-            'x-component': 'ArrayTable.Column',
-            'x-component-props': {
-              width: 200,
-              title: '状态',
-              sorter: (a: any, b: any) => a.state.value.length - b.state.value.length,
-            },
-            properties: {
-              state: {
-                type: 'string',
-                'x-decorator': 'FormItem',
-                'x-component': 'StatusRender',
-              },
-            },
-          },
-          column8: {
-            type: 'void',
-            'x-component': 'ArrayTable.Column',
-            'x-component-props': {
-              title: '操作',
-              dataIndex: 'operations',
-              width: 100,
-              fixed: 'right',
-            },
-            properties: {
-              item: {
-                type: 'void',
-                'x-component': 'FormItem',
-                properties: {
-                  remove: {
-                    type: 'void',
-                    'x-component': 'ActionButton',
-                  },
-                },
-              },
-            },
-          },
-        },
-      },
-    },
-  },
-};
-
-export default () => {
-  return (
-    <FormProvider form={form}>
-      <SchemaField schema={schema} />
-      <FormButtonGroup>
-        <Submit onSubmit={console.log}>提交</Submit>
-      </FormButtonGroup>
-    </FormProvider>
-  );
-};

+ 0 - 70
src/pages/device/Instance/Detail/Modbus/editTable.tsx

@@ -1,70 +0,0 @@
-import { Card, Input, Switch } from 'antd';
-// import { useEffect } from 'react';
-import './index.less';
-import { useDomFullHeight } from '@/hooks';
-import ChannelList from './channelList';
-
-const Editable = () => {
-  const { minHeight } = useDomFullHeight('.modbus');
-
-  // const data = [
-  //   {
-  //     "id": "1657787131289",
-  //     "title": "",
-  //     "decs": "这个活动真好玩",
-  //     "state": "open",
-  //     "created_at": "2020-05-26T09:42:56Z"
-  //   },
-  //   {
-  //     "id": "1657787131290",
-  //     "title": "活动名称1",
-  //     "decs": "这个活动真好玩",
-  //     "state": "open",
-  //     "created_at": "2020-05-26T09:42:56Z"
-  //   },
-  // ]
-  // useEffect(() => {
-  //   const id = data.map((item) => item.id)
-  //   // setEditableRowKeys(id)
-  // }, [])
-  return (
-    <Card className="modbus" style={{ minHeight }}>
-      <div className="edit-top">
-        <Switch checkedChildren={'正常采集'} unCheckedChildren={'停止采集'} defaultChecked />
-        <Input.Search
-          placeholder="请输入属性"
-          allowClear
-          style={{ width: 190 }}
-          onSearch={(value) => {
-            console.log(value);
-          }}
-        />
-      </div>
-      <div className="edit-table">
-        <ChannelList></ChannelList>
-        {/* <EditableProTable
-          rowKey="id"
-          columns={columns}
-          recordCreatorProps={false}
-          value={data}
-          editable={{
-            type: 'multiple',
-            editableKeys,
-            onChange: setEditableRowKeys,
-            onValuesChange: (record) => {
-              console.log(record)
-            },
-            actionRender: (row, config, defaultDoms) => {
-              return [
-                <Button onClick={() => {
-                  console.log(row, config)
-                }}>启用</Button>
-              ];
-            },
-          }}
-        /> */}
-      </div>
-    </Card>
-  );
-};
-export default Editable;

+ 1 - 0
src/pages/device/Instance/Detail/Modbus/index.less

@@ -2,6 +2,7 @@
   display: flex;
   align-items: center;
   justify-content: space-between;
+  margin-bottom: 10px;
 }
 .edit-table {
   .ant-card-body {

+ 508 - 382
src/pages/device/Instance/Detail/Modbus/index.tsx

@@ -1,425 +1,551 @@
+import { FormItem, ArrayTable, Editable, Select, NumberPicker } from '@formily/antd';
+import { createForm, Field, onFieldReact, FormPath, onFieldChange } from '@formily/core';
+import { FormProvider, createSchemaField } from '@formily/react';
+import { Badge, Card, Empty, Input, Tooltip } from 'antd';
+import { action } from '@formily/reactive';
+import type { Response } from '@/utils/typings';
+import './index.less';
+import { useDomFullHeight } from '@/hooks';
 import PermissionButton from '@/components/PermissionButton';
-import { Badge, Card, Empty, Input, Tabs, Tooltip } from 'antd';
-import { useEffect, useRef, useState } from 'react';
-import { useIntl } from 'umi';
-import styles from '@/pages/link/Channel/Opcua/Access/index.less';
-import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
-import {
-  DeleteOutlined,
-  EditOutlined,
-  PlayCircleOutlined,
-  PlusOutlined,
-  StopOutlined,
-} from '@ant-design/icons';
 import { service } from '@/pages/link/Channel/Modbus';
-import Save from '@/pages/link/Channel/Modbus/Save';
-import { InstanceModel } from '@/pages/device/Instance';
-import AddPoint from '@/pages/link/Channel/Modbus/Access/addPoint';
-import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
-import { map } from 'rxjs/operators';
-import { useDomFullHeight } from '@/hooks';
+import { useEffect, useState } from 'react';
+import { DisconnectOutlined, QuestionCircleOutlined } from '@ant-design/icons';
 import { onlyMessage } from '@/utils/util';
 
-const Modbus = () => {
-  const intl = useIntl();
+interface Props {
+  data: any;
+}
+
+export default (props: Props) => {
+  const { data } = props;
+  const { minHeight } = useDomFullHeight('.modbus');
   const { permission } = PermissionButton.usePermission('link/Channel/Modbus');
-  const [bindList, setBindList] = useState<any>([]);
-  const [opcId, setOpcId] = useState<string>('');
-  const actionRef = useRef<ActionType>();
-  const [param, setParam] = useState<any>({});
-  const [visible, setVisible] = useState<boolean>(false);
-  const [channel, setChannel] = useState<any>({});
-  const [pointVisiable, setPointVisiable] = useState<boolean>(false);
-  const [current, setCurrent] = useState<any>({});
-  const [deviceId, setDeviceId] = useState<any>('');
-  const [data, setData] = useState<any>([]);
-  const [subscribeTopic] = useSendWebsocketMessage();
-  const [propertyValue, setPropertyValue] = useState<any>({});
-  const wsRef = useRef<any>();
-  const [filterList, setFilterList] = useState([]);
+  const [properties, setProperties] = useState<any>([]);
+  const [filterList, setFilterList] = useState<any>([]);
+  const [masterList, setMasterList] = useState<any>([]);
+  const [typeList, setTypeList] = useState<any>([]);
+  const [reload, setReload] = useState<string>('');
+  const [empty, setEmpty] = useState<boolean>(false);
 
-  const { minHeight } = useDomFullHeight(`.${styles.list}`);
+  //数据类型长度
+  const lengthMap = new Map();
+  lengthMap.set('int8', 1);
+  lengthMap.set('int16', 2);
+  lengthMap.set('int32', 4);
+  lengthMap.set('int64', 8);
+  lengthMap.set('ieee754_float', 4);
+  lengthMap.set('ieee754_double', 8);
+  lengthMap.set('hex', 1);
 
-  const columns: ProColumns<any>[] = [
-    {
-      title: '属性ID',
-      dataIndex: 'metadataId',
-      ellipsis: true,
-    },
-    {
-      title: '功能码',
-      render: (record: any) => <>{record.function?.text}</>,
-    },
-    {
-      title: '读取起始位置',
-      render: (record: any) => <>{record.codecConfig?.readIndex}</>,
-    },
-    {
-      title: '读取长度',
-      render: (record: any) => <>{record.codecConfig?.readLength}</>,
-    },
-    {
-      title: '值',
-      width: 120,
-      render: (record: any) => <>{propertyValue[record?.metadataId] || '-'}</>,
-    },
-    {
-      title: '状态',
-      width: 90,
-      dataIndex: 'state',
-      renderText: (state) => (
-        <Badge text={state?.text} status={state?.value === 'disabled' ? 'error' : 'success'} />
-      ),
-    },
-    {
-      title: '操作',
-      valueType: 'option',
-      align: 'center',
-      width: 200,
-      render: (text, record) => [
-        <PermissionButton
-          isPermission={permission.update}
-          key="edit"
-          onClick={() => {
-            setPointVisiable(true);
-            setCurrent(record);
-          }}
-          type={'link'}
-          style={{ padding: 0 }}
-          tooltip={{
-            title: intl.formatMessage({
-              id: 'pages.data.option.edit',
-              defaultMessage: '编辑',
-            }),
-          }}
-        >
-          <EditOutlined />
-        </PermissionButton>,
-        <PermissionButton
-          type="link"
-          key={'action'}
-          style={{ padding: 0 }}
-          popConfirm={{
-            title: intl.formatMessage({
-              id: `pages.data.option.${
-                record.state.value !== 'disabled' ? 'disabled' : 'enabled'
-              }.tips`,
-              defaultMessage: '确认禁用?',
-            }),
-            onConfirm: async () => {
-              const item = {
-                ...record,
-                state: record.state.value === 'enabled' ? 'disabled' : 'enabled',
-              };
-              await service.saveMetadataConfig(opcId, deviceId, item);
-              onlyMessage(
-                intl.formatMessage({
-                  id: 'pages.data.option.success',
-                  defaultMessage: '操作成功!',
-                }),
-              );
-              actionRef.current?.reload();
-            },
-          }}
-          isPermission={permission.action}
-          tooltip={{
-            title: intl.formatMessage({
-              id: `pages.data.option.${record.state.value !== 'disabled' ? 'disabled' : 'enabled'}`,
-              defaultMessage: record.state.value !== 'disabled' ? '禁用' : '启用',
-            }),
-          }}
-        >
-          {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
-        </PermissionButton>,
-        <PermissionButton
-          isPermission={permission.delete}
-          style={{ padding: 0 }}
-          disabled={record.state.value === 'enabled'}
-          tooltip={{
-            title:
-              record.state.value === 'disabled'
-                ? intl.formatMessage({
-                    id: 'pages.data.option.remove',
-                    defaultMessage: '删除',
-                  })
-                : '请先禁用该点位,再删除。',
-          }}
-          popConfirm={{
-            title: '确认删除',
-            disabled: record.state.value === 'enabled',
-            onConfirm: async () => {
-              const resp: any = await service.removeMetadataConfig(record.id);
-              if (resp.status === 200) {
-                onlyMessage(
-                  intl.formatMessage({
-                    id: 'pages.data.option.success',
-                    defaultMessage: '操作成功!',
-                  }),
-                );
-                actionRef.current?.reload();
-              }
-            },
-          }}
-          key="delete"
-          type="link"
-        >
-          <DeleteOutlined />
-        </PermissionButton>,
+  const Render = (propsText: any) => {
+    const text = properties.find((item: any) => item.metadataId === propsText.value);
+    return <>{text?.metadataName}</>;
+  };
+  const StatusRender = (propsRender: any) => {
+    if (propsRender.value) {
+      return <Badge status="success" text={'已绑定'} />;
+    } else {
+      return <Badge status="error" text={'未绑定'} />;
+    }
+  };
+  const remove = async (params: any) => {
+    const res = await service.removeDevicePoint(data.id, params);
+    if (res.status === 200) {
+      onlyMessage('解绑成功');
+      setReload('remove');
+    }
+  };
+  const ActionButton = () => {
+    const record = ArrayTable.useRecord?.();
+    const index = ArrayTable.useIndex?.();
+    return (
+      <PermissionButton
+        isPermission={true}
+        style={{ padding: 0 }}
+        disabled={!record(index)?.id}
+        tooltip={{
+          title: '解绑',
+        }}
+        popConfirm={{
+          title: '确认解绑',
+          disabled: !record(index)?.id,
+          onConfirm: async () => {
+            // deteleMaster(item.id)
+            remove([record(index)?.id]);
+          },
+        }}
+        key="unbind"
+        type="link"
+      >
+        <DisconnectOutlined />
+      </PermissionButton>
+    );
+  };
+
+  //异步数据源
+  const getMaster = () =>
+    service.queryMaster({
+      paging: false,
+      sorts: [
+        {
+          name: 'createTime',
+          order: 'desc',
+        },
       ],
-    },
-  ];
+    });
 
-  const getModbus = (id: string) => {
-    service
-      .queryMetadatabyId({
+  const getName = async (field: any) => {
+    const path = FormPath.transform(
+      field.path,
+      /\d+/,
+      (index) => `array.${parseInt(index)}.collectorId`,
+    );
+    const collectorId = field.query(path).get('value');
+    if (!collectorId) return [];
+    return service.getPoint({
+      paging: false,
+      sorts: [
+        {
+          name: 'createTime',
+          order: 'desc',
+        },
+      ],
+      terms: [
+        {
+          column: 'masterId',
+          value: collectorId,
+        },
+      ],
+    });
+  };
+
+  const getQuantity = async (field: any) => {
+    const path = FormPath.transform(
+      field.path,
+      /\d+/,
+      (index) => `array.${parseInt(index)}.collectorId`,
+    );
+    const path1 = FormPath.transform(
+      field.path,
+      /\d+/,
+      (index) => `array.${parseInt(index)}.pointId`,
+    );
+    const collectorId = field.query(path).get('value');
+    const pointId = field.query(path1).get('value');
+    if (collectorId && pointId) {
+      return service.getPoint({
         paging: false,
+        sorts: [
+          {
+            name: 'createTime',
+            order: 'desc',
+          },
+        ],
         terms: [
           {
-            column: 'id$bind-modbus',
-            value: id,
+            column: 'masterId',
+            value: collectorId,
+          },
+          {
+            column: 'id',
+            value: pointId,
           },
         ],
-      })
-      .then((res) => {
-        setBindList(res.result);
-        setFilterList(res.result);
-        setOpcId(res.result?.[0]?.id);
-        setParam({
-          opcId: res.result?.[0]?.id,
-        });
       });
+    } else {
+      return [];
+    }
+  };
+  const useAsync = (api: any) => (field: Field) => {
+    field.loading = true;
+    api(field).then(
+      action.bound!((resp: Response<any>) => {
+        const value = resp.result?.[0].parameter.quantity;
+        field.dataSource = [...new Array(value).keys()].map((item: any) => ({
+          label: item,
+          value: item,
+        }));
+        field.loading = false;
+      }),
+    );
   };
 
-  useEffect(() => {
-    const { id } = InstanceModel.detail;
-    setDeviceId(id);
-    if (id) {
-      getModbus(id);
+  const useAsyncDataSource = (api: any) => (field: Field) => {
+    field.loading = true;
+    api(field).then(
+      action.bound!((resp: Response<any>) => {
+        field.dataSource = resp.result?.map((item: any) => ({
+          label: `${item.name}(${item.unitId}/${item.address}/${item.function.text})`,
+          value: item.id,
+        }));
+        field.loading = false;
+      }),
+    );
+  };
+
+  const save = async (value: any) => {
+    const res = await service.saveDevicePoint(data.id, value);
+    if (res.status === 200) {
+      onlyMessage('保存成功');
+      setReload('save');
     }
-  }, [visible]);
+  };
 
   useEffect(() => {
-    const { id, productId } = InstanceModel.detail;
-    const point = data.map((item: any) => item.metadataId);
-    const wsId = `instance-info-property-${id}-${productId}-${point.join('-')}`;
-    const topic = `/dashboard/device/${productId}/properties/realTime`;
-    wsRef.current = subscribeTopic?.(wsId, topic, {
-      deviceId: deviceId,
-      properties: data.map((item: any) => item.metadataId),
-      history: 1,
-    })
-      ?.pipe(map((res: any) => res.payload))
-      .subscribe((payload: any) => {
-        const { value } = payload;
-        propertyValue[value.property] = value.formatValue;
-        setPropertyValue({ ...propertyValue });
+    service
+      .queryMaster({
+        paging: false,
+        sorts: [
+          {
+            name: 'createTime',
+            order: 'desc',
+          },
+        ],
+      })
+      .then((res) => {
+        if (res.status === 200) {
+          const list = res.result.map((item: any) => ({
+            label: item.name,
+            value: item.id,
+          }));
+          setMasterList(list);
+        }
       });
-  }, [data]);
+    service.dataType().then((res) => {
+      if (res.status === 200) {
+        const items = res.result.map((item: any) => ({
+          label: item.name,
+          value: item.id,
+        }));
+        setTypeList(items);
+      }
+    });
+  }, []);
+  useEffect(() => {
+    const metadata = JSON.parse(data.metadata).properties?.map((item: any) => ({
+      metadataId: item.id,
+      metadataName: `${item.name}(${item.id})`,
+      metadataType: 'property',
+    }));
+    if (metadata && metadata.length !== 0) {
+      service.getDevicePoint(data.id).then((res) => {
+        if (res.status === 200) {
+          // console.log(res.result)
+          const array = res.result.reduce((x: any, y: any) => {
+            const metadataId = metadata.find((item: any) => item.metadataId === y.metadataId);
+            if (metadataId) {
+              Object.assign(metadataId, y);
+            } else {
+              x.push(y);
+            }
+            return x;
+          }, metadata);
+          setProperties(array);
+          setFilterList(array);
+        }
+      });
+    } else {
+      setEmpty(true);
+    }
+  }, [reload]);
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Editable,
+      ArrayTable,
+      Select,
+      NumberPicker,
+      Render,
+      ActionButton,
+      StatusRender,
+    },
+  });
+
+  const form = createForm({
+    values: {
+      array: filterList,
+    },
+    effects: () => {
+      onFieldReact('array.*.collectorId', async (field, f) => {
+        const value = (field as Field).value;
+        const path = FormPath.transform(
+          field.path,
+          /\d+/,
+          (index) => `array.${parseInt(index)}.pointId`,
+        );
+        const path1 = FormPath.transform(
+          field.path,
+          /\d+/,
+          (index) => `array.${parseInt(index)}.codec`,
+        );
+        f.setFieldState(path, (state) => {
+          if (value) {
+            state.required = true;
+            form.validate();
+          } else {
+            state.required = false;
+            form.validate();
+          }
+        });
+        f.setFieldState(path1, (state) => {
+          if (value) {
+            state.required = true;
+            form.validate();
+          } else {
+            state.required = false;
+            form.validate();
+          }
+        });
+      });
+      onFieldChange('array.*.codec', (field: any) => {
+        const value = (field as Field).value;
+        const path = FormPath.transform(
+          field.path,
+          /\d+/,
+          (index) => `array.${parseInt(index)}.codecConfiguration.readIndex`,
+        );
+        if ((field as Field).modified) {
+          const readIndex = field.query(path).get('value');
+          const dataLength = field.query(path).get('dataSource')?.length * 2 - 1;
+          const length = lengthMap.get(value) + readIndex;
+          console.log(length, dataLength);
+          if (length > dataLength) {
+            field.selfErrors = '数据类型对应的长度和起始位置加起来不能超过数据长度';
+          } else {
+            field.selfErrors = '';
+          }
+        }
+      });
+    },
+  });
+
+  const schema = {
+    type: 'object',
+    properties: {
+      array: {
+        type: 'array',
+        'x-decorator': 'FormItem',
+        'x-component': 'ArrayTable',
+        'x-component-props': {
+          pagination: {
+            pageSize: 10,
+          },
+          scroll: { x: '100%' },
+        },
+        items: {
+          type: 'object',
+          properties: {
+            column1: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { width: 120, title: '属性' },
+              properties: {
+                metadataId: {
+                  type: 'string',
+                  'x-component': 'Render',
+                },
+              },
+            },
+            column2: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { width: 200, title: '通道' },
+              properties: {
+                collectorId: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Select',
+                  'x-component-props': {
+                    placeholder: '请选择',
+                    showSearch: true,
+                    allowClear: true,
+                    showArrow: true,
+                    filterOption: (input: string, option: any) =>
+                      option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                  },
+                  enum: masterList,
+                },
+              },
+            },
+            column3: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 200,
+                title: (
+                  <>
+                    点位名称
+                    <Tooltip title="名称(从站ID/地址/功能码))">
+                      <QuestionCircleOutlined />
+                    </Tooltip>
+                  </>
+                ),
+              },
+              properties: {
+                pointId: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Select',
+                  'x-component-props': {
+                    placeholder: '请选择',
+                    showSearch: true,
+                    allowClear: true,
+                    showArrow: true,
+                    filterOption: (input: string, option: any) =>
+                      option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                  },
+                  'x-reactions': ['{{useAsyncDataSource(getName)}}'],
+                },
+              },
+            },
+
+            column4: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 200,
+                title: '起始位置',
+              },
+              properties: {
+                'codecConfiguration.readIndex': {
+                  type: 'string',
+                  default: 0,
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Select',
+                  'x-component-props': {
+                    placeholder: '请选择',
+                    showSearch: true,
+                    allowClear: true,
+                    showArrow: true,
+                    filterOption: (input: string, option: any) =>
+                      option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                  },
+                  'x-reactions': ['{{useAsync(getQuantity)}}'],
+                },
+              },
+            },
+            column5: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 200,
+                title: '数据类型',
+              },
+              properties: {
+                codec: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Select',
+                  'x-component-props': {
+                    placeholder: '请选择',
+                    showSearch: true,
+                    allowClear: true,
+                    showArrow: true,
+                    filterOption: (input: string, option: any) =>
+                      option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                  },
+                  enum: typeList,
+                  // 'x-reactions': {
+                  //   dependencies: ['.codecConfiguration.readIndex'],
+                  //   fulfill: {
+                  //     state: {
+                  //       selfErrors:
+                  //         "{{$deps[0]<lengthMap.get($self.value)?'数据类型对应的长度和起始位置加起来不能超过 (数据长度 - 1)的长度':''}}"
+                  //     }
+                  //   }
+                  // }
+                },
+              },
+            },
+            column6: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 100,
+                title: '状态',
+                sorter: (a: any, b: any) => a.state.value.length - b.state.value.length,
+              },
+              properties: {
+                id: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'StatusRender',
+                },
+              },
+            },
+            column7: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: '操作',
+                dataIndex: 'operations',
+                width: 50,
+                fixed: 'right',
+              },
+              properties: {
+                item: {
+                  type: 'void',
+                  'x-component': 'FormItem',
+                  properties: {
+                    remove: {
+                      type: 'void',
+                      'x-component': 'ActionButton',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  };
 
   return (
-    <Card className={styles.list} style={{ minHeight }}>
-      <div style={{ display: 'flex' }}>
-        <div>
-          <div style={{ width: '250px', marginTop: 15 }}>
+    <Card className="modbus" style={{ minHeight }}>
+      {empty ? (
+        <Empty description={'暂无数据,请配置物模型'} />
+      ) : (
+        <>
+          <div className="edit-top">
             <Input.Search
-              placeholder="请输入通道名称"
+              placeholder="请输入属性ID"
               allowClear
+              style={{ width: 190 }}
               onSearch={(value) => {
                 if (value) {
-                  const items = bindList.filter((item: any) => item.name.match(value));
+                  const items = properties.filter((item: any) => item.metadataId.match(value));
                   setFilterList(items);
-                  if (items.length === 0) {
-                    setParam({
-                      opcId: '',
-                    });
-                  } else {
-                    setParam({
-                      opcId: items[0]?.id,
-                    });
-                    setOpcId(items[0]?.id);
-                  }
                 } else {
-                  setFilterList(bindList);
-                  if (opcId) {
-                    setParam({
-                      opcId: opcId,
-                    });
-                  } else {
-                    setParam({
-                      opcId: '',
-                    });
-                  }
+                  setFilterList(properties);
                 }
               }}
             />
-
             <PermissionButton
-              onClick={() => {
-                setVisible(true);
-                setChannel({});
+              onClick={async () => {
+                const value: any = await form.submit();
+                const items = value.array.filter((item: any) => item.collectorId);
+                save(items);
               }}
               isPermission={permission.add}
               key="add"
-              icon={<PlusOutlined />}
-              type="dashed"
-              style={{ width: '100%', margin: '16px 0 18px 0' }}
+              type="primary"
+              style={{ marginRight: 10 }}
             >
-              新增通道
+              保存
             </PermissionButton>
           </div>
-          {filterList.length > 0 ? (
-            <Tabs
-              style={{ height: 600 }}
-              tabPosition={'left'}
-              activeKey={opcId}
-              onChange={(e) => {
-                setOpcId(e);
-                // console.log(e);
-                // actionRef.current?.reload();
-                setParam({
-                  opcId: e,
-                });
-              }}
-            >
-              {filterList.map((item: any) => (
-                <Tabs.TabPane
-                  key={item.id}
-                  tab={
-                    <div className={styles.left}>
-                      <Tooltip title={item.name}>
-                        <div className={styles.text}>{item.name}</div>
-                      </Tooltip>
-                      <PermissionButton
-                        isPermission={permission.update}
-                        key="edit"
-                        onClick={() => {
-                          setVisible(true);
-                          setChannel(item);
-                        }}
-                        type={'link'}
-                        style={{ padding: 0 }}
-                        tooltip={{
-                          title: intl.formatMessage({
-                            id: 'pages.data.option.edit',
-                            defaultMessage: '编辑',
-                          }),
-                        }}
-                      >
-                        <EditOutlined />
-                      </PermissionButton>
-                      <PermissionButton
-                        isPermission={permission.delete}
-                        style={{ padding: 0 }}
-                        popConfirm={{
-                          title: '确认删除',
-                          onConfirm: async () => {
-                            const resp: any = await service.remove(item.id);
-                            if (resp.status === 200) {
-                              getModbus(deviceId);
-                              onlyMessage(
-                                intl.formatMessage({
-                                  id: 'pages.data.option.success',
-                                  defaultMessage: '操作成功!',
-                                }),
-                              );
-                            }
-                          },
-                        }}
-                        key="delete"
-                        type="link"
-                      >
-                        <DeleteOutlined />
-                      </PermissionButton>
-                    </div>
-                  }
-                ></Tabs.TabPane>
-              ))}
-            </Tabs>
-          ) : (
-            <Empty description={<>暂无绑定通道</>} />
-          )}
-        </div>
-        <div style={{ width: '100%' }}>
-          <ProTable
-            actionRef={actionRef}
-            params={param}
-            columns={columns}
-            rowKey="id"
-            columnEmptyText={''}
-            search={false}
-            headerTitle={
-              <>
-                <PermissionButton
-                  onClick={() => {
-                    setPointVisiable(true);
-                    setCurrent({});
-                  }}
-                  isPermission={permission.add}
-                  key="add"
-                  icon={<PlusOutlined />}
-                  type="primary"
-                >
-                  {intl.formatMessage({
-                    id: 'pages.data.option.add',
-                    defaultMessage: '新增',
-                  })}
-                </PermissionButton>
-              </>
-            }
-            request={async (params) => {
-              if (!params.opcId) {
-                return {
-                  code: 200,
-                  result: {
-                    data: [],
-                    pageIndex: 0,
-                    pageSize: 0,
-                    total: 0,
-                  },
-                  status: 200,
-                };
-              }
-              const res = await service.queryMetadataConfig(params.opcId, deviceId, {
-                ...params.terms,
-                sorts: [{ name: 'createTime', order: 'desc' }],
-              });
-              setData(res.result.data);
-              return {
-                code: res.message,
-                result: {
-                  data: res.result.data,
-                  pageIndex: res.result.pageIndex,
-                  pageSize: res.result.pageSize,
-                  total: res.result.total,
-                },
-                status: res.status,
-              };
-            }}
-          />
-        </div>
-      </div>
-
-      {visible && (
-        <Save
-          data={channel}
-          close={() => {
-            setVisible(false);
-          }}
-          device={InstanceModel.detail}
-        />
-      )}
-      {pointVisiable && (
-        <AddPoint
-          deviceId={deviceId}
-          opcUaId={opcId}
-          data={current}
-          close={() => {
-            setPointVisiable(false);
-            actionRef.current?.reload();
-          }}
-        />
+          <div className="edit-table">
+            <FormProvider form={form}>
+              <SchemaField
+                schema={schema}
+                scope={{ useAsyncDataSource, getName, getMaster, getQuantity, useAsync }}
+              />
+            </FormProvider>
+          </div>
+        </>
       )}
     </Card>
   );
 };
-export default Modbus;

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

@@ -29,7 +29,6 @@ import Service from '@/pages/device/Instance/service';
 import useLocation from '@/hooks/route/useLocation';
 import { onlyMessage } from '@/utils/util';
 import Parsing from './Parsing';
-// import Editable from './Modbus/editTable';
 
 export const deviceStatus = new Map();
 deviceStatus.set('online', <Badge status="success" text={'在线'} />);
@@ -193,8 +192,7 @@ const InstanceDetail = observer(() => {
         datalist.push({
           key: 'modbus',
           tab: 'Modbus',
-          component: <Modbus />,
-          // component: <Editable />,
+          component: <Modbus data={InstanceModel.detail} />,
         });
       }
       if (response.result.protocol === 'opc-ua') {

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

@@ -260,7 +260,7 @@ const Instance = () => {
       width: '200px',
       valueType: 'dateTime',
       render: (_: any, row) => {
-        return row.registryTime ? moment(row.registryTime).format('YYYY-MM-DD HH:mm:ss') : '/';
+        return row.registryTime ? moment(row.registryTime).format('YYYY-MM-DD HH:mm:ss') : '';
       },
       sorter: true,
     },

+ 30 - 36
src/pages/device/Product/Detail/index.tsx

@@ -102,7 +102,7 @@ const ProductDetail = observer(() => {
     'mqtt-server-gateway',
     'tcp-server-gateway',
   ];
-  const [list, setList] = useState<any[]>([...initList]);
+  const [list, setList] = useState<any[]>([]);
 
   const { minHeight } = useDomFullHeight('.product-detail-body');
 
@@ -152,46 +152,40 @@ const ProductDetail = observer(() => {
       });
     });
   };
-
-  useEffect(() => {
-    if (productModel.current?.messageProtocol) {
-      service.getProtocolDetail(productModel.current?.messageProtocol).then((res) => {
-        if (res.status === 200) {
-          const paring = res.result?.transports[0]?.features?.find(
-            (item: any) => item.id === 'transparentCodec',
-          );
-          // console.log(paring)
-          if (paring) {
-            setList([
-              ...initList,
-              {
-                key: 'parsing',
-                tab: intl.formatMessage({
-                  id: 'pages.device.instanceDetail.parsing',
-                  defaultMessage: '数据解析',
-                }),
-                component: <Parsing tag="product" data={productModel.current} />,
-              },
-            ]);
-          }
-        }
-      });
-    }
+  const getDetail = async () => {
     if (
       productModel.current?.accessProvider &&
       pList.includes(productModel.current?.accessProvider)
     ) {
-      setList([
-        ...initList,
-        {
-          key: 'metadata-map',
-          tab: '物模型映射',
-          component: <MetadataMap type="product" />,
-        },
-      ]);
-    } else {
-      setList([...initList]);
+      initList.push({
+        key: 'metadata-map',
+        tab: '物模型映射',
+        component: <MetadataMap type="product" />,
+      });
     }
+    if (productModel.current?.messageProtocol) {
+      const res = await service.getProtocolDetail(productModel.current?.messageProtocol);
+      if (res.status === 200) {
+        const paring = res.result?.transports[0]?.features?.find(
+          (item: any) => item.id === 'transparentCodec',
+        );
+        // console.log(paring)
+        if (paring) {
+          initList.push({
+            key: 'parsing',
+            tab: intl.formatMessage({
+              id: 'pages.device.instanceDetail.parsing',
+              defaultMessage: '数据解析',
+            }),
+            component: <Parsing tag="product" data={productModel.current} />,
+          });
+        }
+      }
+    }
+    setList(initList);
+  };
+  useEffect(() => {
+    getDetail();
   }, [productModel.current]);
 
   useEffect(() => {

+ 18 - 13
src/pages/home/index.tsx

@@ -22,18 +22,19 @@ const Home = () => {
     comprehensive: <Comprehensive />,
   };
 
-  // useEffect(() => {
-  //   service.queryView().then((resp) => {
-  //     // setLoading(false);
-  //     if (resp.status === 200) {
-  //       if (resp.result.length == 0) {
-  //         setCurrent('init');
-  //       } else {
-  //         setCurrent(resp.result[0]?.content);
-  //       }
-  //     }
-  //   });
-  // }, []);
+  const adminView = () => {
+    service
+      .setView({
+        name: 'view',
+        content: 'comprehensive',
+      })
+      .then((res) => {
+        if (res.status === 200) {
+          setCurrent('comprehensive');
+        }
+      });
+  };
+
   useEffect(() => {
     service.userDetail().then((res) => {
       if (res.status === 200) {
@@ -53,7 +54,11 @@ const Home = () => {
                 setLoading(false);
                 if (resp.status === 200) {
                   if (resp.result.length == 0) {
-                    setCurrent('init');
+                    if (response.result.username === 'admin') {
+                      adminView();
+                    } else {
+                      setCurrent('init');
+                    }
                   } else {
                     setCurrent(resp.result[0]?.content);
                   }

+ 76 - 0
src/pages/home/init/accountInit.tsx

@@ -0,0 +1,76 @@
+import { onlyMessage } from '@/utils/util';
+import { Button, Col, Row } from 'antd';
+import classNames from 'classnames';
+import { useEffect, useState } from 'react';
+import { service } from '..';
+import styles from './index.less';
+
+const AccountInit = () => {
+  const [value, setValue] = useState<string>('');
+
+  const viewMap = [
+    {
+      title: '设备接入视图',
+      img: require('/public/images/home/device.png'),
+      value: 'device',
+    },
+    {
+      title: '运维管理视图',
+      img: require('/public/images/home/ops.png'),
+      value: 'ops',
+    },
+    {
+      title: '综合管理视图',
+      img: require('/public/images/home/comprehensive.png'),
+      value: 'comprehensive',
+    },
+  ];
+  useEffect(() => {
+    service.queryView().then((res) => {
+      if (res.status === 200) {
+        setValue(res.result[0]?.content);
+      }
+    });
+  }, []);
+
+  return (
+    <div className={styles.homeBox} style={{ height: '100%', width: '90%' }}>
+      <Row gutter={24}>
+        {viewMap.map((item) => (
+          <Col
+            span={6}
+            key={item.value}
+            onClick={() => {
+              setValue(item.value);
+            }}
+          >
+            <div className={classNames(styles.item, value === item.value ? styles.active : {})}>
+              <img src={item.img} className={styles.item} />
+            </div>
+          </Col>
+        ))}
+      </Row>
+
+      <div style={{ marginTop: 48, marginRight: '25%' }}>
+        <Button
+          type="primary"
+          onClick={() => {
+            service
+              .setView({
+                name: 'view',
+                content: value,
+              })
+              .then((resp) => {
+                if (resp.status === 200) {
+                  onlyMessage('保存成功');
+                }
+              });
+          }}
+        >
+          确定
+        </Button>
+      </div>
+    </div>
+  );
+};
+export default AccountInit;

+ 12 - 12
src/pages/init-home/components/basis.tsx

@@ -1,7 +1,7 @@
 import { UploadImage } from '@/components';
 import { Col, Form, Input, Row, Select } from 'antd';
 import { useEffect, forwardRef, useImperativeHandle } from 'react';
-import { service } from '../index'
+import { service } from '../index';
 
 interface Props {
   getData?: Function;
@@ -11,10 +11,10 @@ const Basis = forwardRef((props: Props, ref) => {
   const [form] = Form.useForm();
 
   const saveData = () => {
-    return new Promise(async resolve => {
+    return new Promise(async (resolve) => {
       const formData = await form.validateFields().catch(() => {
-        resolve(false)
-      })
+        resolve(false);
+      });
       if (formData) {
         const item = [
           {
@@ -31,21 +31,21 @@ const Basis = forwardRef((props: Props, ref) => {
             },
           },
         ];
-        const res = await service.save(item)
+        const res = await service.save(item);
         if (res.status === 200) {
-          resolve(true)
+          resolve(true);
         } else {
-          resolve(false)
+          resolve(false);
         }
       } else {
-        resolve(false)
+        resolve(false);
       }
-    })
-  }
+    });
+  };
 
   useImperativeHandle(ref, () => ({
-    save: saveData
-  }))
+    save: saveData,
+  }));
 
   useEffect(() => {
     if (props.getData) {

Разница между файлами не показана из-за своего большого размера
+ 2453 - 1530
src/pages/init-home/components/data/RoleData.ts


+ 8 - 8
src/pages/init-home/components/data/index.tsx

@@ -1,4 +1,4 @@
-import {forwardRef, useImperativeHandle, useState} from 'react';
+import { forwardRef, useImperativeHandle, useState } from 'react';
 import { service } from '../../index';
 import Save from './save';
 
@@ -10,7 +10,7 @@ const Data = forwardRef((_, ref) => {
   const handleChange = () => {
     return new Promise(async (resolve) => {
       if (!Object.keys(values).length) {
-        return resolve(true)
+        return resolve(true);
       }
       try {
         // 新增网络组件
@@ -61,17 +61,17 @@ const Data = forwardRef((_, ref) => {
           productId: product?.result?.id,
           productName: product?.result?.name,
         });
-        resolve(device.status === 200)
+        resolve(device.status === 200);
       } catch (e) {
-        console.log(e)
-        resolve(false)
+        console.log(e);
+        resolve(false);
       }
-    })
+    });
   };
 
   useImperativeHandle(ref, () => ({
-    save: handleChange
-  }))
+    save: handleChange,
+  }));
 
   return (
     <div>

+ 42 - 39
src/pages/init-home/components/menu.tsx

@@ -1,74 +1,77 @@
-import {useEffect, useRef, useState, forwardRef, useImperativeHandle} from "react";
-import BaseMenu from '@/pages/system/Menu/Setting/baseMenu'
-import {service} from '../index'
+import { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
+import BaseMenu from '@/pages/system/Menu/Setting/baseMenu';
+import { service } from '../index';
 
-const Menu = forwardRef((props: { onChange?: (menu: any) => void}, ref) => {
-
-  const [count, setCount] = useState(0)
-  const menuRef = useRef<any[]>()
+const Menu = forwardRef((props: { onChange?: (menu: any) => void }, ref) => {
+  const [count, setCount] = useState(0);
+  const menuRef = useRef<any[]>();
 
   const menuCount = (menus: any[]) => {
-    return menus.reduce((pre,next) => {
-      let _count = 1
+    return menus.reduce((pre, next) => {
+      let _count = 1;
       if (next.children) {
-        _count = menuCount(next.children)
+        _count = menuCount(next.children);
       }
-      return pre + _count
-    }, 0)
-  }
+      return pre + _count;
+    }, 0);
+  };
 
   /**
    * 根据权限过滤菜单
    */
-  const filterMenu = (permissions:  string[], menus: any[]) => {
-    return menus.filter(item => {
-      let isPermissions = true
+  const filterMenu = (permissions: string[], menus: any[]) => {
+    return menus.filter((item) => {
+      let isPermissions = true;
       if (item.permissions && item.permissions.length) {
-        isPermissions = item.permissions.some((pItem: any) => permissions.includes(pItem.permission))
+        isPermissions = item.permissions.some((pItem: any) =>
+          permissions.includes(pItem.permission),
+        );
       }
 
       if (item.children) {
-        item.children = filterMenu(permissions, item.children)
+        item.children = filterMenu(permissions, item.children);
       }
 
-      return isPermissions || !!item.children?.length
-    })
-  }
-
+      return isPermissions || !!item.children?.length;
+    });
+  };
 
   /**
    * 获取当前系统所有权限
    */
   const getSystemPermissions = async () => {
-    const resp = await service.getPermissionAll()
+    const resp = await service.getPermissionAll();
     if (resp.status === 200) {
-      const newTree = filterMenu(resp.result.map((item: any) => item.id), BaseMenu)
-      const _count = menuCount(newTree)
-      menuRef.current = newTree
-      setCount(_count)
+      const newTree = filterMenu(
+        resp.result.map((item: any) => item.id),
+        BaseMenu,
+      );
+      const _count = menuCount(newTree);
+      menuRef.current = newTree;
+      setCount(_count);
     }
-  }
+  };
 
   useImperativeHandle(ref, () => ({
     save: () => {
       return new Promise((resolve) => {
         if (props.onChange) {
-          props.onChange(menuRef.current)
+          props.onChange(menuRef.current);
         }
-        service.updateMenus(menuRef.current).then(res => {
-          resolve(res.status === 200)
-        })
-      })
-    }
-  }))
+        service.updateMenus(menuRef.current).then((res) => {
+          resolve(res.status === 200);
+        });
+      });
+    },
+  }));
 
   useEffect(() => {
-    getSystemPermissions()
-  }, [])
+    getSystemPermissions();
+  }, []);
 
   return (
     <div style={{ display: 'flex', alignItems: 'center' }}>
-      <div style={{ marginRight: 16}}>
+      <div style={{ marginRight: 16 }}>
         <img src={require('/public/images/init-home/menu.png')} />
       </div>
       <div>
@@ -79,4 +82,4 @@ const Menu = forwardRef((props: { onChange?: (menu: any) => void}, ref) => {
   );
 });
 
-export default Menu ;
+export default Menu;

+ 76 - 67
src/pages/init-home/components/role.tsx

@@ -1,103 +1,112 @@
-import {Checkbox} from 'antd'
-import { useState, forwardRef, useImperativeHandle} from "react";
-import classNames from "classnames";
-import RoleMenuData, {roleKeysType, ROLEKEYS, RoleData } from "./data/RoleData"
-import {service} from "@/pages/init-home";
+import { Checkbox } from 'antd';
+import { useState, forwardRef, useImperativeHandle } from 'react';
+import classNames from 'classnames';
+import RoleMenuData, { roleKeysType, ROLEKEYS, RoleData } from './data/RoleData';
+import { service } from '@/pages/init-home';
 import '../index.less';
 
 const Role = forwardRef((_, ref) => {
-
-  const [ keys, setKeys ] = useState<roleKeysType[]>([])
+  const [keys, setKeys] = useState<roleKeysType[]>([]);
 
   const findMenuByRole = (menu: any[], code: string): any => {
-    let _item = null
-    menu.some(item => {
+    let _item = null;
+    menu.some((item) => {
       if (item.code === code) {
-        _item = item
-        return true
+        _item = item;
+        return true;
       }
 
       if (item.children) {
-        const childrenItem = findMenuByRole(item.children, code)
+        const childrenItem = findMenuByRole(item.children, code);
         if (childrenItem) {
-          _item = childrenItem
-          return true
+          _item = childrenItem;
+          return true;
         }
-        return false
+        return false;
       }
 
-      return null
-    })
-    return _item
-  }
+      return null;
+    });
+    return _item;
+  };
 
-  useImperativeHandle(ref, () => ({
-    save: async () => {
-      return new Promise((resolve) => {
-        if (!keys.length) {
-          return resolve(true)
-        }
-        let Count = 0
-        keys.forEach(async (item, index) => {
-          const _itemData = RoleData[item]
-          // 添加该角色
-          const res = await service.addRole(_itemData)
-          if (res.status === 200) {
-            const menuTree = await service.getRoleMenu(res.result.id)
-            if (menuTree.status === 200) {
-              const _roleData = (RoleMenuData[item] as []).filter((roleItem: any) => {
-                const _menu = findMenuByRole(menuTree.result, roleItem.code)
-                if (_menu) {
-                  roleItem.id = _menu.id
-                  roleItem.parentId = _menu.parentId
-                  roleItem.createTime = _menu.createTime
-                  return true
+  useImperativeHandle(
+    ref,
+    () => ({
+      save: async () => {
+        return new Promise((resolve) => {
+          if (!keys.length) {
+            return resolve(true);
+          }
+          let Count = 0;
+          keys.forEach(async (item, index) => {
+            const _itemData = RoleData[item];
+            // 添加该角色
+            const res = await service.addRole(_itemData);
+            if (res.status === 200) {
+              const menuTree = await service.getRoleMenu(res.result.id);
+              if (menuTree.status === 200) {
+                const _roleData = (RoleMenuData[item] as []).filter((roleItem: any) => {
+                  const _menu = findMenuByRole(menuTree.result, roleItem.code);
+                  if (_menu) {
+                    roleItem.id = _menu.id;
+                    roleItem.parentId = _menu.parentId;
+                    roleItem.createTime = _menu.createTime;
+                    return true;
+                  }
+                  return false;
+                });
+                //更新权限
+                const roleRes = await service.updateRoleMenu(res.result.id, { menus: _roleData });
+                if (roleRes.status === 200) {
+                  Count += 1;
                 }
-                return false
-              })
-              //更新权限
-              const roleRes = await service.updateRoleMenu(res.result.id, {menus: _roleData})
-              if (roleRes.status === 200) {
-                Count += 1
-              }
-              if (index === keys.length - 1) {
-                resolve(Count === keys.length)
+                if (index === keys.length - 1) {
+                  resolve(Count === keys.length);
+                }
+              } else if (index === keys.length - 1) {
+                resolve(Count === keys.length);
               }
-            } else if (index === keys.length - 1){
-              resolve(Count === keys.length)
+            } else if (index === keys.length - 1) {
+              resolve(Count === keys.length);
             }
-          } else if (index === keys.length - 1){
-            resolve(Count === keys.length)
-          }
-        })
-      })
-    }
-  }), [keys])
+          });
+        });
+      },
+    }),
+    [keys],
+  );
 
   return (
     <div className={'init-home-role'}>
-      <Checkbox.Group onChange={(e) => {
-        setKeys(e as roleKeysType[])
-      }}>
+      <Checkbox.Group
+        onChange={(e) => {
+          setKeys(e as roleKeysType[]);
+        }}
+      >
         <div className={'init-home-role-content'}>
-          <div className={classNames('role-item role-image-1', {active: keys.includes('device')})}>
-            <div className={'role-item-title'} >
+          <div
+            className={classNames('role-item role-image-1', { active: keys.includes('device') })}
+          >
+            <div className={'role-item-title'}>
               <Checkbox value={ROLEKEYS.device}></Checkbox>
               <div>设备接入岗</div>
             </div>
             <div className={'role-item-content'}></div>
             <div className={'role-item-footer'}>该角色负责设备接入模块的维护管理</div>
           </div>
-          <div className={classNames('role-item role-image-2', {active: keys.includes('link')})}>
-            <div className={'role-item-title'} >
+          <div className={classNames('role-item role-image-2', { active: keys.includes('link') })}>
+            <div className={'role-item-title'}>
               <Checkbox value={ROLEKEYS.link}></Checkbox>
               <div>运维管理岗</div>
             </div>
             <div className={'role-item-content'}></div>
             <div className={'role-item-footer'}>该角色负责系统运维模块的维护管理</div>
           </div>
-          <div className={classNames('role-item role-image-3', {active: keys.includes('complex')})}>
-            <div className={'role-item-title'} >
+          <div
+            className={classNames('role-item role-image-3', { active: keys.includes('complex') })}
+          >
+            <div className={'role-item-title'}>
               <Checkbox value={ROLEKEYS.complex}></Checkbox>
               <div>综合管理岗</div>
             </div>

+ 38 - 30
src/pages/init-home/index.tsx

@@ -1,40 +1,40 @@
 import { TitleComponent } from '@/components';
-import {Button, Collapse, Spin} from 'antd';
+import { Button, Collapse, Spin } from 'antd';
 import styles from './index.less';
 import Basis from './components/basis';
 import Menu from './components/menu';
 import Role from './components/role';
 import Data from './components/data';
 import Service from './service';
-import { useHistory } from 'umi'
-import {useState, useRef, useEffect} from 'react';
-import BaseMenu from '@/pages/system/Menu/Setting/baseMenu'
+import { useHistory } from 'umi';
+import { useState, useRef, useEffect } from 'react';
+import BaseMenu from '@/pages/system/Menu/Setting/baseMenu';
 
 export const service = new Service();
 
 const InitHome = () => {
   const [loadings, setLoadings] = useState<boolean>(false);
   const [, setCurrent] = useState<number>(0);
-  const history = useHistory()
+  const history = useHistory();
 
-  const cacheRef = useRef<Set<string>>()
+  const cacheRef = useRef<Set<string>>();
 
-  const baseRef = useRef<{ save: any }>()
-  const menuRef = useRef<{ save: any }>()
-  const roleRef = useRef<{ save: any }>()
-  const dataRef = useRef<{ save: any }>()
+  const baseRef = useRef<{ save: any }>();
+  const menuRef = useRef<{ save: any }>();
+  const roleRef = useRef<{ save: any }>();
+  const dataRef = useRef<{ save: any }>();
 
   const jump = () => {
-    history.push(BaseMenu[0].url)
-  }
+    history.push(BaseMenu[0].url);
+  };
 
   useEffect(() => {
-    service.getInit().then(res => {
+    service.getInit().then((res) => {
       if (res.status === 200 && res.result.length) {
         // jump()
       }
-    })
-  }, [])
+    });
+  }, []);
 
   return (
     <div className={styles.init}>
@@ -105,34 +105,42 @@ const InitHome = () => {
                 setLoadings(true);
                 setCurrent(0);
                 if (!cacheRef.current?.has('base')) {
-                  const baseRes = await baseRef.current?.save()
-                  if (!baseRes) { return  setLoadings(false) }
-                  cacheRef.current?.add('base')
+                  const baseRes = await baseRef.current?.save();
+                  if (!baseRes) {
+                    return setLoadings(false);
+                  }
+                  cacheRef.current?.add('base');
                 }
 
                 if (!cacheRef.current?.has('menu')) {
-                  const menuRes = await menuRef.current?.save()
-                  if (!menuRes) { return  setLoadings(false) }
-                  cacheRef.current?.add('menu')
+                  const menuRes = await menuRef.current?.save();
+                  if (!menuRes) {
+                    return setLoadings(false);
+                  }
+                  cacheRef.current?.add('menu');
                 }
 
                 if (!cacheRef.current?.has('role')) {
-                  const roleRes = await roleRef.current?.save()
-                  if (!roleRes) { return  setLoadings(false) }
-                  cacheRef.current?.add('role')
+                  const roleRes = await roleRef.current?.save();
+                  if (!roleRes) {
+                    return setLoadings(false);
+                  }
+                  cacheRef.current?.add('role');
                 }
 
                 if (!cacheRef.current?.has('data')) {
-                  const dataRes = await dataRef.current?.save()
-                  if (!dataRes) { return  setLoadings(false) }
-                  cacheRef.current?.add('data')
+                  const dataRes = await dataRef.current?.save();
+                  if (!dataRes) {
+                    return setLoadings(false);
+                  }
+                  cacheRef.current?.add('data');
                 }
                 //  记录当前
-                service.saveInit().then(res => {
+                service.saveInit().then((res) => {
                   if (res.status === 200) {
-                    jump()
+                    jump();
                   }
-                })
+                });
               }}
             >
               确认

+ 13 - 9
src/pages/init-home/service.ts

@@ -49,24 +49,28 @@ class Service extends BaseService<any> {
     request(`${SystemConst.API_BASE}/permission/_query/no-paging?paging=false`);
 
   // 更新全部菜单
-  updateMenus = (data: any) => request(`${SystemConst.API_BASE}/menu/_all`, { method: 'PATCH', data });
+  updateMenus = (data: any) =>
+    request(`${SystemConst.API_BASE}/menu/_all`, { method: 'PATCH', data });
 
   // 添加角色
-  addRole = (data: any) =>
-    request(`/${SystemConst.API_BASE}/role`, { method: 'POST', data })
+  addRole = (data: any) => request(`/${SystemConst.API_BASE}/role`, { method: 'POST', data });
 
   // 更新权限菜单
   getRoleMenu = (id: string) =>
-    request(`/${SystemConst.API_BASE}/menu/role/${id}/_grant/tree`, { method: 'GET' })
+    request(`/${SystemConst.API_BASE}/menu/role/${id}/_grant/tree`, { method: 'GET' });
 
   // 更新权限菜单
   updateRoleMenu = (id: string, data: any) =>
-    request(`/${SystemConst.API_BASE}/menu/role/${id}/_grant`, { method: 'PUT', data })
+    request(`/${SystemConst.API_BASE}/menu/role/${id}/_grant`, { method: 'PUT', data });
 
-//  记录初始化
-  saveInit = () => request(`/${SystemConst.API_BASE}/user/settings/init`, { method: 'POST', data: { init: true} })
-//  获取初始化
-  getInit = () => request(`/${SystemConst.API_BASE}/user/settings/init`, { method: 'GET' })
+  //  记录初始化
+  saveInit = () =>
+    request(`/${SystemConst.API_BASE}/user/settings/init`, {
+      method: 'POST',
+      data: { init: true },
+    });
+  //  获取初始化
+  getInit = () => request(`/${SystemConst.API_BASE}/user/settings/init`, { method: 'GET' });
 }
 
 export default Service;

+ 121 - 0
src/pages/link/Channel/Modbus/Export/index.tsx

@@ -0,0 +1,121 @@
+import { FormItem, FormLayout, Radio, Select } from '@formily/antd';
+import { createForm } from '@formily/core';
+import { createSchemaField, FormProvider } from '@formily/react';
+import { Modal } from 'antd';
+import 'antd/lib/tree-select/style/index.less';
+import { useEffect, useState } from 'react';
+import { service } from '@/pages/link/Channel/Modbus';
+import SystemConst from '@/utils/const';
+import { downloadFile } from '@/utils/util';
+
+interface Props {
+  visible: boolean;
+  close: () => void;
+  masterId?: any;
+}
+
+const Export = (props: Props) => {
+  const { visible, close } = props;
+  const [list, setList] = useState<any[]>([]);
+  const SchemaField = createSchemaField({
+    components: {
+      Radio,
+      Select,
+      FormItem,
+      FormLayout,
+    },
+  });
+
+  useEffect(() => {
+    service.queryMaster({ paging: false }).then((resp) => {
+      if (resp.status === 200) {
+        const items = resp.result.map((item: { name: any; id: any }) => ({
+          label: item.name,
+          value: item.id,
+        }));
+        setList(items);
+      }
+    });
+  }, []);
+
+  const form = createForm();
+
+  const schema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-component': 'FormLayout',
+        'x-component-props': {
+          // labelCol: 4,
+          // wrapperCol: 18,
+          // labelAlign: 'right',
+          layout: 'vertical',
+        },
+        properties: {
+          masterId: {
+            type: 'string',
+            title: '通道',
+            required: true,
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            enum: [...list],
+            'x-component-props': {
+              allowClear: true,
+              showSearch: true,
+              showArrow: true,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+            },
+          },
+          fileType: {
+            title: '文件格式',
+            default: 'xlsx',
+            'x-decorator': 'FormItem',
+            'x-component': 'Radio.Group',
+            'x-component-props': {
+              optionType: 'button',
+              buttonStyle: 'solid',
+            },
+            enum: [
+              {
+                label: 'xlsx',
+                value: 'xlsx',
+              },
+              {
+                label: 'csv',
+                value: 'csv',
+              },
+            ],
+          },
+        },
+      },
+    },
+  };
+  const downloadTemplate = async () => {
+    const values = (await form.submit()) as any;
+    if (values) {
+      downloadFile(
+        `/${SystemConst.API_BASE}/modbus/point/${values.masterId}/export.${values.fileType}`,
+      );
+      close();
+    }
+  };
+  return (
+    <Modal
+      maskClosable={false}
+      visible={visible}
+      onCancel={() => close()}
+      width="35vw"
+      title="导出"
+      onOk={downloadTemplate}
+    >
+      <div style={{ marginTop: '20px' }}>
+        <FormProvider form={form}>
+          <SchemaField schema={schema} />
+        </FormProvider>
+      </div>
+    </Modal>
+  );
+};
+export default Export;

+ 225 - 0
src/pages/link/Channel/Modbus/import/index.tsx

@@ -0,0 +1,225 @@
+import { FormItem, FormLayout, Select } from '@formily/antd';
+import { createForm } from '@formily/core';
+import { createSchemaField, FormProvider } from '@formily/react';
+import { Badge, Button, Modal, Radio, Space, Upload } from 'antd';
+import { useEffect, useState } from 'react';
+import SystemConst from '@/utils/const';
+import Token from '@/utils/token';
+import { downloadFile, onlyMessage } from '@/utils/util';
+import { UploadOutlined } from '@ant-design/icons';
+import { EventSourcePolyfill } from 'event-source-polyfill';
+
+interface Props {
+  visible: boolean;
+  close: () => void;
+  masterId: any;
+}
+const FileFormat = (props: any) => {
+  const [data, setData] = useState<{ autoDeploy: boolean; fileType: 'xlsx' | 'csv' }>({
+    autoDeploy: false,
+    fileType: 'xlsx',
+  });
+  return (
+    <Space>
+      <Radio.Group
+        defaultValue="xlsx"
+        buttonStyle="solid"
+        onChange={(e) => {
+          setData({
+            ...data,
+            fileType: e.target.value,
+          });
+          props.onChange({
+            ...data,
+            fileType: e.target.value,
+          });
+        }}
+      >
+        <Radio.Button value="xlsx">xlsx</Radio.Button>
+        <Radio.Button value="csv">csv</Radio.Button>
+      </Radio.Group>
+    </Space>
+  );
+};
+const NormalUpload = (props: any) => {
+  const [importLoading, setImportLoading] = useState(false);
+  const [flag, setFlag] = useState<boolean>(true);
+  const [count, setCount] = useState<number>(0);
+  const [errMessage, setErrMessage] = useState<string>('');
+  const [errorUrl, setErrorUrl] = useState<string>('');
+
+  const submitData = async (fileUrl: string) => {
+    setErrorUrl(fileUrl);
+    if (!!fileUrl) {
+      setCount(0);
+      setErrMessage('');
+      setFlag(true);
+      setImportLoading(true);
+      let dt = 0;
+      const source = new EventSourcePolyfill(
+        `/${SystemConst.API_BASE}/modbus/point/${
+          props.masterId
+        }/import?fileUrl=${fileUrl}&:X_Access_Token=${Token.get()}`,
+      );
+      source.onmessage = (e: any) => {
+        const res = JSON.parse(e.data);
+        if (res.success) {
+          props.onChange(false);
+          const temp = res.result.total;
+          dt += temp;
+          setCount(dt);
+        } else {
+          setErrMessage(res.message || '失败');
+        }
+      };
+      source.onerror = () => {
+        setFlag(false);
+        source.close();
+      };
+      source.onopen = () => {};
+    } else {
+      onlyMessage('请先上传文件', 'error');
+    }
+  };
+  return (
+    <div>
+      <Space>
+        <Upload
+          action={`/${SystemConst.API_BASE}/file/static`}
+          accept={'.xlsx, .csv'}
+          headers={{
+            'X-Access-Token': Token.get(),
+          }}
+          onChange={async (info) => {
+            if (info.file.status === 'done') {
+              const resp: any = info.file.response || { result: '' };
+              await submitData(resp?.result || '');
+            }
+          }}
+          showUploadList={false}
+        >
+          <Button icon={<UploadOutlined />}>上传文件</Button>
+        </Upload>
+        <div style={{ marginLeft: 20 }}>
+          下载模板
+          <a
+            style={{ marginLeft: 10 }}
+            onClick={() => {
+              const url = `/${SystemConst.API_BASE}/modbus/point/template.xlsx`;
+              downloadFile(url);
+            }}
+          >
+            .xlsx
+          </a>
+          <a
+            style={{ marginLeft: 10 }}
+            onClick={() => {
+              const url = `/${SystemConst.API_BASE}/modbus/point/template.csv`;
+              downloadFile(url);
+            }}
+          >
+            .csv
+          </a>
+        </div>
+      </Space>
+      {importLoading && (
+        <div style={{ marginLeft: 20 }}>
+          {flag ? (
+            <Badge status="processing" text="进行中" />
+          ) : (
+            <Badge status="success" text="已完成" />
+          )}
+          <span style={{ marginLeft: 15 }}>总数量:{count}</span>
+          <div>
+            {errMessage && (
+              <>
+                <Badge status="error" text="失败" />
+                <span style={{ marginLeft: 15 }}>{errMessage}</span>
+                <a href={errorUrl} style={{ marginLeft: 15 }}>
+                  下载
+                </a>
+              </>
+            )}
+          </div>
+        </div>
+      )}
+    </div>
+  );
+};
+const Import = (props: Props) => {
+  const { visible, close, masterId } = props;
+
+  useEffect(() => {
+    console.log(masterId);
+  }, []);
+
+  const schema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-component': 'FormLayout',
+        'x-component-props': {
+          // labelCol: 6,
+          // wrapperCol: 18,
+          // labelAlign: 'right',
+          layout: 'vertical',
+        },
+        properties: {
+          fileType: {
+            title: '文件格式',
+            // 'x-visible': false,
+            'x-decorator': 'FormItem',
+            'x-component': 'FileFormat',
+          },
+          upload: {
+            type: 'string',
+            title: '文件上传',
+            // 'x-visible': false,
+            'x-decorator': 'FormItem',
+            'x-component': 'NormalUpload',
+            'x-component-props': {
+              masterId: masterId,
+            },
+          },
+        },
+      },
+    },
+  };
+  const SchemaField = createSchemaField({
+    components: {
+      Radio,
+      Select,
+      FormItem,
+      FormLayout,
+      FileFormat,
+      NormalUpload,
+    },
+  });
+  const form = createForm({});
+  return (
+    <Modal
+      maskClosable={false}
+      visible={visible}
+      onCancel={() => close()}
+      width="35vw"
+      title="导入"
+      onOk={() => close()}
+      footer={[
+        <Button key="cancel" onClick={() => close()}>
+          取消
+        </Button>,
+        <Button key="ok" type="primary" onClick={() => close()}>
+          确认
+        </Button>,
+      ]}
+    >
+      <div style={{ marginTop: '10px' }}>
+        <FormProvider form={form}>
+          <SchemaField schema={schema} />
+        </FormProvider>
+      </div>
+    </Modal>
+  );
+};
+export default Import;

+ 276 - 101
src/pages/link/Channel/Modbus/index.tsx

@@ -1,4 +1,4 @@
-import { Badge, Button, Card, Divider, Dropdown, Input, Menu } from 'antd';
+import { Badge, Button, Card, Divider, Dropdown, Input, Menu, Modal } from 'antd';
 import { useDomFullHeight } from '@/hooks';
 import './index.less';
 import SearchComponent from '@/components/SearchComponent';
@@ -11,15 +11,22 @@ import {
   ImportOutlined,
   PlayCircleOutlined,
   PlusOutlined,
+  SearchOutlined,
   StopOutlined,
 } from '@ant-design/icons';
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import { useIntl } from 'umi';
 import ChannelCard from '../channelCard';
 import { PageContainer } from '@ant-design/pro-layout';
 import Service from './service';
 import SaveChannel from './saveChannel';
 import SavePoint from './savePoint';
+import Import from './import';
+import { onlyMessage } from '@/utils/util';
+import Export from './Export';
+import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
+import { map } from 'rxjs/operators';
+import Ellipsis from '@/components/Ellipsis';
 
 export const service = new Service('');
 
@@ -32,26 +39,50 @@ const NewModbus = () => {
   const [activeKey, setActiveKey] = useState<any>('');
   const [visible, setVisible] = useState<boolean>(false);
   const [visiblePoint, setVisiblePoint] = useState<boolean>(false);
+  const [exportVisible, setExportVisible] = useState<boolean>(false);
   const [current, setCurrent] = useState<any>({});
   const [pointDetail, setPointDetail] = useState<any>({});
-  const data = [
-    {
-      id: 1,
-      status: 'connect',
-      state: {
-        text: '正常',
-        value: 'enabled',
-      },
-    },
-    {
-      id: 2,
-      status: 'disconnect',
-      state: {
-        text: '禁用',
-        value: 'disabled',
-      },
-    },
-  ];
+  const [importVisible, setImportVisible] = useState<boolean>(false);
+  const [masterList, setMasterList] = useState<any>([]);
+  const [filterList, setFilterList] = useState([]);
+  const masterId = useRef<string>('');
+  const [subscribeTopic] = useSendWebsocketMessage();
+  const wsRef = useRef<any>();
+  const [pointList, setPointList] = useState<any>([]);
+  const [currentData, setCurrentData] = useState<any>({});
+
+  const collectMap = new Map();
+  collectMap.set('running', 'success');
+  collectMap.set('error', 'error');
+  collectMap.set('stopped', 'warning');
+
+  const menu = (
+    <Menu>
+      <Menu.Item key="1">
+        <PermissionButton
+          isPermission={permission.export || true}
+          icon={<ExportOutlined />}
+          type="default"
+          onClick={() => {
+            setExportVisible(true);
+          }}
+        >
+          批量导出点位
+        </PermissionButton>
+      </Menu.Item>
+      <Menu.Item key="2">
+        <PermissionButton
+          isPermission={permission.import || true}
+          icon={<ImportOutlined />}
+          onClick={() => {
+            setImportVisible(true);
+          }}
+        >
+          批量导入点位
+        </PermissionButton>
+      </Menu.Item>
+    </Menu>
+  );
 
   const columns: ProColumns<any>[] = [
     {
@@ -63,37 +94,80 @@ const NewModbus = () => {
     },
     {
       title: '功能码',
-      dataIndex: 'host',
+      render: (record: any) => <>{record.function?.text}</>,
     },
     {
       title: '从站ID',
-      dataIndex: 'port',
+      dataIndex: 'unitId',
       search: false,
-      valueType: 'digit',
     },
     {
       title: '寄存器数量',
-      dataIndex: 'port',
       search: false,
-      valueType: 'digit',
+      render: (record: any) => <>{record.parameter?.quantity}</>,
     },
     {
       title: '地址',
-      dataIndex: 'port',
+      dataIndex: 'address',
       search: false,
-      valueType: 'digit',
     },
     {
       title: '当前数据',
-      dataIndex: 'port',
       search: false,
-      valueType: 'digit',
+      // ellipsis: true,
+      width: 150,
+      render: (record: any) => (
+        <a
+          onClick={() => {
+            if (currentData[record?.id]) {
+              const arr = currentData[record?.id].match(/[\S]{1,4}/g);
+              Modal.info({
+                title: '当前数据',
+                content: (
+                  <div style={{ display: 'flex', flexDirection: 'column' }}>
+                    {arr.map((item: any, index: number) => (
+                      <div>
+                        寄存器{index + 1}: {item}
+                      </div>
+                    ))}
+                  </div>
+                ),
+              });
+            }
+          }}
+        >
+          <Ellipsis title={currentData[record?.id] || '-'} />
+        </a>
+      ),
     },
     {
       title: '采集状态',
-      dataIndex: 'port',
       search: false,
-      valueType: 'digit',
+      render: (record: any) => (
+        <>
+          {record.state.value === 'disabled' ? (
+            '-'
+          ) : (
+            <>
+              <Badge
+                status={collectMap.get(record.collectState?.value)}
+                text={record.collectState?.text}
+              />
+              {record.collectState?.value === 'error' && (
+                <SearchOutlined
+                  style={{ color: '#1d39c4', marginLeft: 3 }}
+                  onClick={() => {
+                    Modal.error({
+                      title: '失败原因',
+                      content: <div>{record.errorReason}</div>,
+                    });
+                  }}
+                />
+              )}
+            </>
+          )}
+        </>
+      ),
     },
     {
       title: '状态',
@@ -128,8 +202,8 @@ const NewModbus = () => {
           isPermission={permission.update}
           key="edit"
           onClick={() => {
-            // setVisible(true);
-            // setCurrent(record);
+            setPointDetail(record);
+            setVisiblePoint(true);
           }}
           type={'link'}
           style={{ padding: 0 }}
@@ -154,24 +228,24 @@ const NewModbus = () => {
               defaultMessage: '确认禁用?',
             }),
             onConfirm: async () => {
-              //   if (record.state.value === 'disabled') {
-              //     await service.edit({
-              //       ...record,
-              //       state: 'enabled',
-              //     });
-              //   } else {
-              //     await service.edit({
-              //       ...record,
-              //       state: 'disabled',
-              //     });
-              //   }
-              //   onlyMessage(
-              //     intl.formatMessage({
-              //       id: 'pages.data.option.success',
-              //       defaultMessage: '操作成功!',
-              //     }),
-              //   );
-              //   actionRef.current?.reload();
+              if (record.state.value === 'disabled') {
+                await service.editPoint(record.id, {
+                  ...record,
+                  state: 'enabled',
+                });
+              } else {
+                await service.editPoint(record.id, {
+                  ...record,
+                  state: 'disabled',
+                });
+              }
+              onlyMessage(
+                intl.formatMessage({
+                  id: 'pages.data.option.success',
+                  defaultMessage: '操作成功!',
+                }),
+              );
+              actionRef.current?.reload();
             },
           }}
           isPermission={permission.action}
@@ -192,16 +266,16 @@ const NewModbus = () => {
             title: '确认删除',
             disabled: record.state.value === 'enabled',
             onConfirm: async () => {
-              //   const resp: any = await service.remove(record.id);
-              //   if (resp.status === 200) {
-              //     onlyMessage(
-              //       intl.formatMessage({
-              //         id: 'pages.data.option.success',
-              //         defaultMessage: '操作成功!',
-              //       }),
-              //     );
-              //     actionRef.current?.reload();
-              //   }
+              const resp: any = await service.deletePoint(record.id);
+              if (resp.status === 200) {
+                onlyMessage(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }
             },
           }}
           key="delete"
@@ -213,33 +287,71 @@ const NewModbus = () => {
     },
   ];
 
-  const menu = (
-    <Menu>
-      <Menu.Item key="1">
-        <PermissionButton
-          isPermission={permission.export}
-          icon={<ExportOutlined />}
-          type="default"
-          onClick={() => {
-            // setExportVisible(true);
-          }}
-        >
-          批量导出设备
-        </PermissionButton>
-      </Menu.Item>
-      <Menu.Item key="2">
-        <PermissionButton
-          isPermission={permission.import}
-          icon={<ImportOutlined />}
-          onClick={() => {
-            // setImportVisible(true);
-          }}
-        >
-          批量导入设备
-        </PermissionButton>
-      </Menu.Item>
-    </Menu>
-  );
+  const getMaster = () => {
+    service
+      .queryMaster({
+        paging: false,
+        sorts: [
+          {
+            name: 'createTime',
+            order: 'desc',
+          },
+        ],
+      })
+      .then((res: any) => {
+        if (res.status === 200) {
+          setMasterList(res.result);
+          setFilterList(res.result);
+          setActiveKey(res.result?.[0]?.id);
+          masterId.current = res.result?.[0]?.id;
+          console.log(masterId.current);
+        }
+      });
+  };
+
+  //启用禁用
+  const actions = (id: string, data: any) => {
+    service.editMaster(id, data).then((res) => {
+      if (res.status === 200) {
+        onlyMessage('操作成功');
+        getMaster();
+      }
+    });
+  };
+
+  const deteleMaster = (id: string) => {
+    service.deleteMaster(id).then((res) => {
+      if (res.status === 200) {
+        onlyMessage('删除成功');
+        getMaster();
+      }
+    });
+  };
+
+  useEffect(() => {
+    masterId.current = activeKey;
+    actionRef.current?.reload();
+  }, [activeKey]);
+
+  useEffect(() => {
+    getMaster();
+  }, []);
+
+  useEffect(() => {
+    const id = `collector-data-modbus`;
+    const topic = `/collector/MODBUS_TCP/${activeKey}/data`;
+    wsRef.current = subscribeTopic?.(id, topic, {
+      pointId: pointList.map((item: any) => item.id),
+    })
+      ?.pipe(map((res: any) => res.payload))
+      .subscribe((payload: any) => {
+        const { pointId, hex } = payload;
+        current[pointId] = hex;
+        setCurrentData({ ...current });
+        console.log(current);
+      });
+    return () => wsRef.current && wsRef.current?.unsubscribe();
+  }, [pointList]);
 
   return (
     <PageContainer>
@@ -251,7 +363,14 @@ const NewModbus = () => {
                 placeholder="请输入名称"
                 allowClear
                 onSearch={(value) => {
-                  console.log(value);
+                  const items = masterList.filter((item: any) => item.name.match(value));
+                  if (value) {
+                    setFilterList(items);
+                    setActiveKey(items?.[0].id);
+                  } else {
+                    setFilterList(masterList);
+                    setActiveKey(masterList?.[0].id);
+                  }
                 }}
               />
               <PermissionButton
@@ -263,12 +382,12 @@ const NewModbus = () => {
                 key="add"
                 icon={<PlusOutlined />}
                 type="default"
-                style={{ width: '100%', marginTop: 16 }}
+                style={{ width: '100%', marginTop: 16, marginBottom: 16 }}
               >
                 新增
               </PermissionButton>
               <div className="item-left-list">
-                {data.map((item) => (
+                {filterList.map((item: any) => (
                   <ChannelCard
                     active={activeKey === item.id}
                     data={item}
@@ -281,8 +400,8 @@ const NewModbus = () => {
                           isPermission={permission.update}
                           key="edit"
                           onClick={() => {
-                            // setVisible(true);
-                            // setCurrent(record);
+                            setVisible(true);
+                            setCurrent(item);
                           }}
                           type={'link'}
                           style={{ padding: 0 }}
@@ -303,7 +422,13 @@ const NewModbus = () => {
                               }.tips`,
                               defaultMessage: '确认禁用?',
                             }),
-                            onConfirm: async () => {},
+                            onConfirm: async () => {
+                              if (item.state.value === 'disabled') {
+                                actions(item.id, { state: 'enabled' });
+                              } else {
+                                actions(item.id, { state: 'disabled' });
+                              }
+                            },
                           }}
                         >
                           {item.state.value === 'enabled' ? (
@@ -318,10 +443,15 @@ const NewModbus = () => {
                           isPermission={permission.delete}
                           style={{ padding: 0 }}
                           disabled={item.state.value === 'enabled'}
+                          tooltip={{
+                            title: item.state.value === 'enabled' ? '请先禁用该通道,再删除。' : '',
+                          }}
                           popConfirm={{
                             title: '确认删除',
                             disabled: item.state.value === 'enabled',
-                            onConfirm: async () => {},
+                            onConfirm: async () => {
+                              deteleMaster(item.id);
+                            },
                           }}
                           key="delete"
                           type="link"
@@ -349,8 +479,8 @@ const NewModbus = () => {
               params={param}
               columns={columns}
               rowKey="id"
-              columnEmptyText={''}
-              // scroll={{ x: 1000 }}
+              // dataSource={dataSoure}
+              scroll={{ x: 200 }}
               search={false}
               headerTitle={
                 <>
@@ -375,9 +505,36 @@ const NewModbus = () => {
                   </Dropdown>
                 </>
               }
-              // request={async (params) =>
-              //     service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
-              // }
+              request={async (params) => {
+                if (masterId.current) {
+                  const res = await service.queryPoint(masterId.current, {
+                    ...params,
+                    sorts: [{ name: 'createTime', order: 'desc' }],
+                  });
+                  setPointList(res.result.data);
+                  return {
+                    code: res.message,
+                    result: {
+                      data: res.result.data,
+                      pageIndex: res.result.pageIndex,
+                      pageSize: res.result.pageSize,
+                      total: res.result.total,
+                    },
+                    status: res.status,
+                  };
+                } else {
+                  return {
+                    code: 200,
+                    result: {
+                      data: [],
+                      pageIndex: 0,
+                      pageSize: 0,
+                      total: 0,
+                    },
+                    status: 200,
+                  };
+                }
+              }}
             />
           </div>
         </div>
@@ -387,19 +544,37 @@ const NewModbus = () => {
           data={current}
           close={() => {
             setVisible(false);
-            actionRef.current?.reload();
+            getMaster();
+            // actionRef.current?.reload();
           }}
         />
       )}
       {visiblePoint && (
         <SavePoint
           data={pointDetail}
+          masterId={activeKey}
           close={() => {
             setVisiblePoint(false);
             actionRef.current?.reload();
           }}
         />
       )}
+      <Import
+        masterId={activeKey}
+        close={() => {
+          setImportVisible(false);
+          actionRef.current?.reload();
+        }}
+        visible={importVisible}
+      />
+      <Export
+        masterId={activeKey}
+        close={() => {
+          setExportVisible(false);
+          actionRef.current?.reload();
+        }}
+        visible={exportVisible}
+      />
     </PageContainer>
   );
 };

+ 16 - 2
src/pages/link/Channel/Modbus/saveChannel.tsx

@@ -2,9 +2,10 @@ import { createForm } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import { Form, FormGrid, FormItem, Input, NumberPicker, Select } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
-// import { service } from '@/pages/link/Channel/Modbus';
+import { service } from '@/pages/link/Channel/Modbus';
 import { Modal } from '@/components';
 import { useEffect } from 'react';
+import { onlyMessage } from '@/utils/util';
 // import { onlyMessage } from '@/utils/util';
 
 interface Props {
@@ -126,7 +127,20 @@ const SaveChannel = (props: Props) => {
 
   const save = async () => {
     const value = await form.submit<any>();
-    console.log(value);
+    if (props.data.id) {
+      console.log(value);
+      const res = await service.editMaster(props.data.id, value);
+      if (res.status === 200) {
+        onlyMessage('保存成功');
+        props.close();
+      }
+    } else {
+      const res = await service.saveMaster(value);
+      if (res.status === 200) {
+        onlyMessage('保存成功');
+        props.close();
+      }
+    }
   };
 
   useEffect(() => {

+ 32 - 12
src/pages/link/Channel/Modbus/savePoint.tsx

@@ -1,11 +1,13 @@
 import { Col, Form, Input, InputNumber, Modal, Row, Select } from 'antd';
 // import { useEffect, useState } from 'react';
-// import { service } from '@/pages/link/Channel/Modbus';
+import { service } from '@/pages/link/Channel/Modbus';
+import { onlyMessage } from '@/utils/util';
 // import { onlyMessage } from '@/utils/util';
 
 interface Props {
   data: any;
   close: Function;
+  masterId: string;
 }
 
 const SavePoint = (props: Props) => {
@@ -13,7 +15,31 @@ const SavePoint = (props: Props) => {
 
   const handleSave = async () => {
     const formData = await form.validateFields();
-    console.log(formData);
+    if (props.data.id) {
+      service
+        .editPoint(props.data.id, {
+          masterId: props.masterId,
+          ...formData,
+        })
+        .then((res) => {
+          if (res.status === 200) {
+            onlyMessage('保存成功');
+            props.close();
+          }
+        });
+    } else {
+      service
+        .savePoint({
+          masterId: props.masterId,
+          ...formData,
+        })
+        .then((res) => {
+          if (res.status === 200) {
+            onlyMessage('保存成功');
+            props.close();
+          }
+        });
+    }
   };
 
   return (
@@ -30,15 +56,9 @@ const SavePoint = (props: Props) => {
       <Form
         form={form}
         layout="vertical"
-        // initialValues={{
-        //     ...props.data,
-        //     codecConfig: {
-        //         ...props.data?.codecConfig,
-        //         readIndex: props.data?.codecConfig?.readIndex || 0,
-        //         scaleFactor: props.data?.codecConfig?.scaleFactor || 1,
-        //         revertBytes: props.data?.codecConfig?.revertBytes || false,
-        //     },
-        // }}
+        initialValues={{
+          ...props.data,
+        }}
       >
         <Row gutter={[24, 24]}>
           <Col span={24}>
@@ -92,7 +112,7 @@ const SavePoint = (props: Props) => {
           <Col span={24}>
             <Form.Item
               label="寄存器数量"
-              name="quantity"
+              name={['parameter', 'quantity']}
               rules={[
                 { required: true, message: '请输入寄存器数量' },
                 ({}) => ({

+ 63 - 30
src/pages/link/Channel/Modbus/service.ts

@@ -2,56 +2,89 @@ import BaseService from '@/utils/BaseService';
 import { request } from 'umi';
 import SystemConst from '@/utils/const';
 
-class Service extends BaseService<OpaUa> {
-  edit = (data: any) =>
+class Service extends BaseService<any> {
+  saveMaster = (data: any) =>
     request(`/${SystemConst.API_BASE}/modbus/master`, {
-      method: 'PATCH',
+      method: 'POST',
       data,
     });
-  bindDevice = (params: any) =>
-    request(`/${SystemConst.API_BASE}/device-instance/_query/no-paging?paging=false`, {
-      method: 'GET',
-      params,
+  editMaster = (id: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/modbus/master/${id}`, {
+      method: 'PUT',
+      data,
     });
-  getDevice = (params?: any) =>
-    request(`/${SystemConst.API_BASE}/device-instance/_query`, {
+  queryMaster = (data: any) =>
+    request(`/${SystemConst.API_BASE}/modbus/master/_query/no-paging`, {
       method: 'POST',
-      data: params,
+      data,
     });
-  bind = (params: any, id: string) =>
-    request(`/${SystemConst.API_BASE}/modbus/master/${id}/_bind`, {
+  deleteMaster = (id: string) =>
+    request(`/${SystemConst.API_BASE}/modbus/master/${id}`, {
+      method: 'DELETE',
+    });
+  savePoint = (data: any) =>
+    request(`/${SystemConst.API_BASE}/modbus/point`, {
       method: 'POST',
-      data: params,
+      data,
     });
-  unbind = (params: any, id: string) =>
-    request(`/${SystemConst.API_BASE}/modbus/master/${id}/_unbind`, {
+  queryPoint = (masterId: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/modbus/point/${masterId}/_query`, {
       method: 'POST',
-      data: params,
+      data,
     });
-  deviceDetail = (id: any) =>
-    request(`/${SystemConst.API_BASE}/device-instance/${id}/detail`, {
-      method: 'GET',
+  editPoint = (id: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/modbus/point/${id}`, {
+      method: 'PUT',
+      data,
+    });
+  deletePoint = (id: string) =>
+    request(`/${SystemConst.API_BASE}/modbus/point/${id}`, {
+      method: 'DELETE',
     });
-  saveMetadataConfig = (master: string, deviceId: string, data: any) =>
-    request(`/${SystemConst.API_BASE}/modbus/master/${master}/${deviceId}/metadata`, {
+  //查询modbus点位列表
+  getPoint = (data: any) =>
+    request(`/${SystemConst.API_BASE}/modbus/point/_query/no-paging`, {
       method: 'POST',
       data,
     });
-  queryMetadataConfig = (master: string, deviceId: string, data: any) =>
-    request(`/${SystemConst.API_BASE}/modbus/master/${master}/${deviceId}/metadata/_query`, {
-      method: 'POST',
+  //保存设备绑定点位映射配置
+  // saveDevicePoint = (deviceId: string, data: any) =>
+  //   request(`/${SystemConst.API_BASE}/device/instance/${deviceId}/collector/modbus`, {
+  //     method: 'PATCH',
+  //     data,
+  //   });
+  saveDevicePoint = (deviceId: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/things/collector/device/${deviceId}/MODBUS_TCP`, {
+      method: 'PATCH',
       data,
     });
-  removeMetadataConfig = (metadataId: string) =>
-    request(`/${SystemConst.API_BASE}/modbus/master/metadata/${metadataId}`, {
-      method: 'DELETE',
+  //查询设备点位映射配置
+  // getDevicePoint = (deviceId: string, param?: string) =>
+  //   request(`/${SystemConst.API_BASE}/device/instance/${deviceId}/collector/_query`, {
+  //     method: 'GET',
+  //     param,
+  //   });
+  getDevicePoint = (deviceId: string, param?: string) =>
+    request(`/${SystemConst.API_BASE}/things/collector/device/${deviceId}/_query`, {
+      method: 'GET',
+      param,
     });
-  //设备id查modbus通道
-  queryMetadatabyId = (data: any) =>
-    request(`/${SystemConst.API_BASE}/modbus/master/_query/no-paging`, {
+  //设备解绑点位映射配置
+  // removeDevicePoint = (deviceId: string, data: any) =>
+  //   request(`/${SystemConst.API_BASE}/device/instance/${deviceId}/collectors/_delete`, {
+  //     method: 'POST',
+  //     data,
+  //   });
+  removeDevicePoint = (deviceId: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/things/collector/device/${deviceId}/_delete`, {
       method: 'POST',
       data,
     });
+  //modbus数据类型
+  dataType = () =>
+    request(`/${SystemConst.API_BASE}/things/collector/codecs`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

+ 387 - 157
src/pages/link/Channel/Opcua/index.tsx

@@ -1,67 +1,137 @@
-import { PageContainer } from '@ant-design/pro-layout';
+import { Badge, Button, Card, Divider, Dropdown, Input, Menu, Modal } from 'antd';
+import { useDomFullHeight } from '@/hooks';
+import './index.less';
+import SearchComponent from '@/components/SearchComponent';
 import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Badge, Card, Col, Row } from 'antd';
-import styles from './index.less';
-import { PermissionButton } from '@/components';
-import { history, useIntl } from 'umi';
+import PermissionButton from '@/components/PermissionButton';
 import {
-  ControlOutlined,
   DeleteOutlined,
   EditOutlined,
+  ExportOutlined,
+  ImportOutlined,
   PlayCircleOutlined,
   PlusOutlined,
+  SearchOutlined,
   StopOutlined,
 } from '@ant-design/icons';
-import { useRef, useState } from 'react';
-import SearchComponent from '@/components/SearchComponent';
+import { useEffect, useRef, useState } from 'react';
+import { useIntl } from 'umi';
+import ChannelCard from '../channelCard';
+import { PageContainer } from '@ant-design/pro-layout';
 import Service from './service';
-import Save from './Save';
-import { getMenuPathByCode } from '@/utils/menu';
-import { useDomFullHeight } from '@/hooks';
+import SaveChannel from './saveChannel';
+import SavePoint from './savePoint';
+// import Import from './import';
 import { onlyMessage } from '@/utils/util';
 
 export const service = new Service('opc/client');
 
-const Opcua = () => {
+const NewModbus = () => {
+  const { minHeight } = useDomFullHeight(`.modbus`);
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
+  const { permission } = PermissionButton.usePermission('link/Channel/Modbus');
   const [param, setParam] = useState({});
-  const { permission } = PermissionButton.usePermission('link/Channel/Opcua');
+  const [activeKey, setActiveKey] = useState<any>('');
   const [visible, setVisible] = useState<boolean>(false);
-  const [current, setCurrent] = useState<Partial<OpaUa>>({});
-  const { minHeight } = useDomFullHeight(`.opcua`, 24);
+  const [visiblePoint, setVisiblePoint] = useState<boolean>(false);
+  const [current, setCurrent] = useState<any>({});
+  const [pointDetail, setPointDetail] = useState<any>({});
+  // const [importVisible, setImportVisible] = useState<boolean>(false);
+  const [masterList, setMasterList] = useState<any>([]);
+  const [filterList, setFilterList] = useState([]);
+  const masterId = useRef<string>('');
 
-  const iconMap = new Map();
-  iconMap.set('1', require('/public/images/channel/1.png'));
-  iconMap.set('2', require('/public/images/channel/2.png'));
-  iconMap.set('3', require('/public/images/channel/3.png'));
-  iconMap.set('4', require('/public/images/channel/4.png'));
+  const collectMap = new Map();
+  collectMap.set('running', 'success');
+  collectMap.set('error', 'error');
+  collectMap.set('stopped', 'warning');
+
+  const menu = (
+    <Menu>
+      <Menu.Item key="1">
+        <PermissionButton
+          isPermission={permission.export}
+          icon={<ExportOutlined />}
+          type="default"
+          onClick={() => {
+            // setExportVisible(true);
+          }}
+        >
+          批量导出设备
+        </PermissionButton>
+      </Menu.Item>
+      <Menu.Item key="2">
+        <PermissionButton
+          isPermission={permission.import || true}
+          icon={<ImportOutlined />}
+          onClick={() => {
+            // setImportVisible(true);
+          }}
+        >
+          批量导入设备
+        </PermissionButton>
+      </Menu.Item>
+    </Menu>
+  );
 
-  const columns: ProColumns<OpaUa>[] = [
+  const columns: ProColumns<any>[] = [
     {
-      title: '通道名称',
+      title: '名称',
       dataIndex: 'name',
-      fixed: 'left',
-      width: 300,
       ellipsis: true,
+      width: 200,
+      fixed: 'left',
+    },
+    {
+      title: '点位Id',
+      render: (record: any) => <>{record.function?.text}</>,
+    },
+    {
+      title: '数据类型',
+      dataIndex: 'unitId',
+      search: false,
     },
     {
-      title: '服务地址',
-      // dataIndex: 'clientConfigs',
-      render: (_, record) => <>{record.clientConfigs?.[0].endpoint}</>,
+      title: '当前数据',
+      search: false,
+      render: (record: any) => <>{record.parameter?.quantity}</>,
     },
     {
-      title: '安全策略',
-      render: (_, record) => <>{record.clientConfigs?.[0].securityPolicy}</>,
+      title: '采集状态',
+      search: false,
+      render: (record: any) => (
+        <>
+          {record.state.value === 'disabled' ? (
+            '-'
+          ) : (
+            <>
+              <Badge
+                status={collectMap.get(record.collectState?.value)}
+                text={record.collectState?.text}
+              />
+              <SearchOutlined
+                style={{ color: '#1d39c4', marginLeft: 3 }}
+                onClick={() => {
+                  Modal.error({
+                    title: '失败原因',
+                    content: <div>111111</div>,
+                    onOk() {},
+                  });
+                }}
+              />
+            </>
+          )}
+        </>
+      ),
     },
     {
       title: '状态',
       dataIndex: 'state',
-      valueType: 'select',
-      width: 100,
       renderText: (state) => (
         <Badge text={state?.text} status={state?.value === 'disabled' ? 'error' : 'success'} />
       ),
+      valueType: 'select',
       valueEnum: {
         disabled: {
           text: intl.formatMessage({
@@ -71,10 +141,7 @@ const Opcua = () => {
           status: 'disabled',
         },
         enabled: {
-          text: intl.formatMessage({
-            id: 'pages.device.product.status.enabled',
-            defaultMessage: '正常',
-          }),
+          text: '正常',
           status: 'enabled',
         },
       },
@@ -84,15 +151,15 @@ const Opcua = () => {
       title: '操作',
       valueType: 'option',
       align: 'center',
+      width: 120,
       fixed: 'right',
-      width: 200,
       render: (text, record) => [
         <PermissionButton
           isPermission={permission.update}
           key="edit"
           onClick={() => {
-            setVisible(true);
-            setCurrent(record);
+            setPointDetail(record);
+            setVisiblePoint(true);
           }}
           type={'link'}
           style={{ padding: 0 }}
@@ -118,28 +185,23 @@ const Opcua = () => {
             }),
             onConfirm: async () => {
               if (record.state.value === 'disabled') {
-                const res = await service.enable(record.id);
-                if (res.status === 200) {
-                  onlyMessage(
-                    intl.formatMessage({
-                      id: 'pages.data.option.success',
-                      defaultMessage: '操作成功!',
-                    }),
-                  );
-                  actionRef.current?.reload();
-                }
+                await service.editPoint(record.id, {
+                  ...record,
+                  state: 'enabled',
+                });
               } else {
-                const res = await service.disable(record.id);
-                if (res.status === 200) {
-                  onlyMessage(
-                    intl.formatMessage({
-                      id: 'pages.data.option.success',
-                      defaultMessage: '操作成功!',
-                    }),
-                  );
-                  actionRef.current?.reload();
-                }
+                await service.editPoint(record.id, {
+                  ...record,
+                  state: 'disabled',
+                });
               }
+              onlyMessage(
+                intl.formatMessage({
+                  id: 'pages.data.option.success',
+                  defaultMessage: '操作成功!',
+                }),
+              );
+              actionRef.current?.reload();
             },
           }}
           isPermission={permission.action}
@@ -153,20 +215,6 @@ const Opcua = () => {
           {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         <PermissionButton
-          isPermission={permission.view}
-          style={{ padding: 0 }}
-          key="link"
-          type="link"
-          tooltip={{
-            title: '数据点绑定',
-          }}
-          onClick={() => {
-            history.push(`${getMenuPathByCode('link/Channel/Opcua/Access')}?id=${record.id}`);
-          }}
-        >
-          <ControlOutlined />
-        </PermissionButton>,
-        <PermissionButton
           isPermission={permission.delete}
           style={{ padding: 0 }}
           disabled={record.state.value === 'enabled'}
@@ -174,7 +222,7 @@ const Opcua = () => {
             title: '确认删除',
             disabled: record.state.value === 'enabled',
             onConfirm: async () => {
-              const resp: any = await service.remove(record.id);
+              const resp: any = await service.deletePoint(record.id);
               if (resp.status === 200) {
                 onlyMessage(
                   intl.formatMessage({
@@ -195,101 +243,283 @@ const Opcua = () => {
     },
   ];
 
-  const topCard = [
-    {
-      numeber: '1',
-      title: 'OPC UA通道',
-      text: '配置OPC UA通道',
-    },
-    {
-      numeber: '2',
-      title: '设备接入网关',
-      text: '创建OPC UA设备接入网关',
-    },
-    {
-      numeber: '3',
-      title: '创建产品',
-      text: '创建产品,并选择接入方式为OPC UA',
-    },
-    {
-      numeber: '4',
-      title: '添加设备',
-      text: '添加设备,单独为每一个设备进行数据点绑定',
-    },
-  ];
+  const getOpc = () => {
+    service
+      .noPagingOpcua({
+        paging: false,
+        sorts: [
+          {
+            name: 'createTime',
+            order: 'desc',
+          },
+        ],
+      })
+      .then((res: any) => {
+        if (res.status === 200) {
+          setMasterList(res.result);
+          setFilterList(res.result);
+          setActiveKey(res.result?.[0]?.id);
+          masterId.current = res.result?.[0]?.id;
+          console.log(masterId.current);
+        }
+      });
+  };
+
+  //启用
+  const _start = (id: string) => {
+    service.enable(id).then((res) => {
+      if (res.status === 200) {
+        onlyMessage('操作成功');
+        getOpc();
+      }
+    });
+  };
+  //禁用
+  const _stop = (id: string) => {
+    service.disable(id).then((res) => {
+      if (res.status === 200) {
+        onlyMessage('操作成功');
+        getOpc();
+      }
+    });
+  };
+
+  const removeOpc = (id: string) => {
+    service.remove(id).then((res: any) => {
+      if (res.status === 200) {
+        onlyMessage('删除成功');
+        getOpc();
+      }
+    });
+  };
+
+  useEffect(() => {
+    masterId.current = activeKey;
+    actionRef.current?.reload();
+  }, [activeKey]);
+
+  useEffect(() => {
+    getOpc();
+  }, []);
+
   return (
     <PageContainer>
-      <Card style={{ marginBottom: 10 }}>
-        <Row gutter={[24, 24]}>
-          {topCard.map((item) => (
-            <Col span={6} key={item.numeber}>
-              <Card>
-                <div className={styles.topCard}>
-                  <div>
-                    <img src={iconMap.get(item.numeber)} />
-                  </div>
-                  <div className={styles.text}>
-                    <p className={styles.p1}>{item.title}</p>
-                    <p className={styles.p2}>{item.text}</p>
-                  </div>
-                </div>
-              </Card>
-            </Col>
-          ))}
-        </Row>
+      <Card className="modbus" style={{ minHeight }}>
+        <div className="item">
+          <div className="item-left">
+            <div style={{ width: 220 }}>
+              <Input.Search
+                placeholder="请输入名称"
+                allowClear
+                onSearch={(value) => {
+                  const items = masterList.filter((item: any) => item.name.match(value));
+                  if (value) {
+                    setFilterList(items);
+                    setActiveKey(items?.[0].id);
+                  } else {
+                    setFilterList(masterList);
+                    setActiveKey(masterList?.[0].id);
+                  }
+                }}
+              />
+              <PermissionButton
+                onClick={() => {
+                  setVisible(true);
+                  setCurrent({});
+                }}
+                isPermission={permission.add}
+                key="add"
+                icon={<PlusOutlined />}
+                type="default"
+                style={{ width: '100%', marginTop: 16, marginBottom: 16 }}
+              >
+                新增
+              </PermissionButton>
+              <div className="item-left-list">
+                {filterList.map((item: any) => (
+                  <ChannelCard
+                    active={activeKey === item.id}
+                    data={item}
+                    onClick={() => {
+                      setActiveKey(item.id);
+                    }}
+                    actions={
+                      <>
+                        <PermissionButton
+                          isPermission={permission.update}
+                          key="edit"
+                          onClick={() => {
+                            setVisible(true);
+                            setCurrent(item);
+                          }}
+                          type={'link'}
+                          style={{ padding: 0 }}
+                        >
+                          <EditOutlined />
+                          编辑
+                        </PermissionButton>
+                        <Divider type="vertical" />
+                        <PermissionButton
+                          isPermission={permission.update}
+                          key="enbale"
+                          type={'link'}
+                          style={{ padding: 0 }}
+                          popConfirm={{
+                            title: intl.formatMessage({
+                              id: `pages.data.option.${
+                                item.state.value !== 'disabled' ? 'disabled' : 'enabled'
+                              }.tips`,
+                              defaultMessage: '确认禁用?',
+                            }),
+                            onConfirm: async () => {
+                              if (item.state.value === 'disabled') {
+                                _start(item.id);
+                              } else {
+                                _stop(item.id);
+                              }
+                            },
+                          }}
+                        >
+                          {item.state.value === 'enabled' ? (
+                            <StopOutlined />
+                          ) : (
+                            <PlayCircleOutlined />
+                          )}
+                          {item.state.value === 'enabled' ? '禁用' : '启用'}
+                        </PermissionButton>
+                        <Divider type="vertical" />
+                        <PermissionButton
+                          isPermission={permission.delete}
+                          style={{ padding: 0 }}
+                          disabled={item.state.value === 'enabled'}
+                          tooltip={{
+                            title: item.state.value === 'enabled' ? '请先禁用该通道,再删除。' : '',
+                          }}
+                          popConfirm={{
+                            title: '确认删除',
+                            disabled: item.state.value === 'enabled',
+                            onConfirm: async () => {
+                              removeOpc(item.id);
+                            },
+                          }}
+                          key="delete"
+                          type="link"
+                        >
+                          <DeleteOutlined />
+                        </PermissionButton>
+                      </>
+                    }
+                  />
+                ))}
+              </div>
+            </div>
+          </div>
+          <div className="item-right">
+            <SearchComponent<any>
+              field={columns}
+              target="modbus"
+              onSearch={(value) => {
+                actionRef.current?.reset?.();
+                setParam(value);
+              }}
+            />
+            <ProTable
+              actionRef={actionRef}
+              params={param}
+              columns={columns}
+              rowKey="id"
+              // dataSource={dataSoure}
+              // scroll={{ x: 1000 }}
+              search={false}
+              headerTitle={
+                <>
+                  <PermissionButton
+                    onClick={() => {
+                      setPointDetail({});
+                      setVisiblePoint(true);
+                    }}
+                    isPermission={permission.add}
+                    key="add"
+                    icon={<PlusOutlined />}
+                    type="primary"
+                    style={{ marginRight: 10 }}
+                  >
+                    {intl.formatMessage({
+                      id: 'pages.data.option.add',
+                      defaultMessage: '新增',
+                    })}
+                  </PermissionButton>
+                  <Dropdown key={'more'} overlay={menu} placement="bottom">
+                    <Button>批量操作</Button>
+                  </Dropdown>
+                </>
+              }
+              // request={async (params) => {
+              //   if (masterId.current) {
+              //     const res = await service.queryPoint(masterId.current, {
+              //       ...params,
+              //       sorts: [{ name: 'createTime', order: 'desc' }],
+              //     });
+              //     return {
+              //       code: res.message,
+              //       result: {
+              //         data: res.result.data,
+              //         pageIndex: res.result.pageIndex,
+              //         pageSize: res.result.pageSize,
+              //         total: res.result.total,
+              //       },
+              //       status: res.status,
+              //     };
+              //   } else {
+              //     return {
+              //       code: 200,
+              //       result: {
+              //         data: [],
+              //         pageIndex: 0,
+              //         pageSize: 0,
+              //         total: 0,
+              //       },
+              //       status: 200,
+              //     };
+              //   }
+              // }}
+              // request={async (params) =>
+              //   service.queryPoint(masterId.current,{
+              // ...params,
+              // sorts: [{ name: 'createTime', order: 'desc' }] })
+              // }
+            />
+          </div>
+        </div>
       </Card>
-
-      <SearchComponent<any>
-        field={columns}
-        target="opcua"
-        onSearch={(data) => {
-          // 重置分页数据
-          actionRef.current?.reset?.();
-          setParam(data);
-        }}
-      />
-      <ProTable<OpaUa>
-        actionRef={actionRef}
-        params={param}
-        scroll={{ x: 1366 }}
-        columns={columns}
-        rowKey="id"
-        search={false}
-        columnEmptyText={''}
-        tableClassName={'opcua'}
-        tableStyle={{ minHeight }}
-        headerTitle={
-          <PermissionButton
-            onClick={() => {
-              // setMode('add');
-              setVisible(true);
-              setCurrent({});
-            }}
-            isPermission={permission.add}
-            key="add"
-            icon={<PlusOutlined />}
-            type="primary"
-          >
-            {intl.formatMessage({
-              id: 'pages.data.option.add',
-              defaultMessage: '新增',
-            })}
-          </PermissionButton>
-        }
-        request={async (params) =>
-          service.query({ ...params, sorts: [{ name: 'id', order: 'desc' }] })
-        }
-      />
       {visible && (
-        <Save
+        <SaveChannel
           data={current}
           close={() => {
             setVisible(false);
+            getOpc();
+          }}
+        />
+      )}
+      {visiblePoint && (
+        <SavePoint
+          data={pointDetail}
+          opcId={activeKey}
+          close={() => {
+            setVisiblePoint(false);
             actionRef.current?.reload();
           }}
         />
       )}
+      {/* <Import
+        data={current}
+        close={() => {
+          setImportVisible(false);
+          actionRef.current?.reload();
+        }}
+        visible={importVisible}
+      /> */}
     </PageContainer>
   );
 };
-export default Opcua;
+export default NewModbus;

+ 250 - 0
src/pages/link/Channel/Opcua/saveChannel.tsx

@@ -0,0 +1,250 @@
+import { createForm, Field } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Form, FormGrid, FormItem, Input, NumberPicker, Select } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+import { service } from '@/pages/link/Channel/Opcua';
+import { Modal } from '@/components';
+import { onlyMessage } from '@/utils/util';
+import { action } from '@formily/reactive';
+import type { Response } from '@/utils/typings';
+
+interface Props {
+  data: any;
+  close: () => void;
+}
+
+const SaveChannel = (props: Props) => {
+  const form = createForm({
+    initialValues: {
+      ...props.data,
+    },
+  });
+
+  const useAsyncDataSource = (api: any) => (field: Field) => {
+    field.loading = true;
+    api(field).then(
+      action.bound!((resp: Response<any>) => {
+        field.dataSource = resp.result?.map((item: Record<string, unknown>) => ({
+          label: item,
+          value: item,
+        }));
+        field.loading = false;
+      }),
+    );
+  };
+
+  const getPolicies = () => service.policies();
+  const getModes = () => service.modes();
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      FormGrid,
+      NumberPicker,
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
+        'x-decorator-props': {
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
+        },
+        properties: {
+          name: {
+            title: '名称',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入名称',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入名称',
+              },
+            ],
+          },
+          'configuration.endpoint': {
+            title: '服务地址',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入服务地址',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入服务地址',
+              },
+              {
+                pattern:
+                  '(opc.tcp|http|https|opc.http|opc.https|opc.ws|opc.wss)://([^:/]+|\\[.*])(:\\d+)?(/.*)?',
+                message: '以固定协议(http,https,opc.tcp等)字段开头,并用://与IP地址连接',
+              },
+            ],
+            name: 'endpoint',
+            required: true,
+          },
+          'configuration.securityPolicy': {
+            title: '安全策略',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-component-props': {
+              placeholder: '请选择安全策略',
+              showArrow: true,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择安全策略',
+              },
+            ],
+            'x-reactions': ['{{useAsyncDataSource(getPolicies)}}'],
+          },
+          'configuration.securityMode': {
+            title: '安全模式',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-component-props': {
+              placeholder: '请选择安全模式',
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择安全模式',
+              },
+            ],
+            required: true,
+            'x-reactions': ['{{useAsyncDataSource(getModes)}}'],
+          },
+          'configuration.username': {
+            title: '用户名',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入用户名',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
+          },
+          'configuration.password': {
+            title: '密码',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入密码',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
+          },
+          description: {
+            title: '说明',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input.TextArea',
+            'x-component-props': {
+              rows: 5,
+              placeholder: '请输入说明',
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                max: 200,
+                message: '最多可输入200个字符',
+              },
+            ],
+          },
+        },
+      },
+    },
+  };
+
+  const save = async () => {
+    const value = await form.submit<any>();
+    if (props.data.id) {
+      const res = await service.editOpc(props.data.id, value);
+      if (res.status === 200) {
+        onlyMessage('保存成功');
+        props.close();
+      }
+    } else {
+      const res = await service.saveOpc(value);
+      if (res.status === 200) {
+        onlyMessage('保存成功');
+        props.close();
+      }
+    }
+  };
+
+  return (
+    <Modal
+      title={props.data.id ? '编辑通道' : '新增通道'}
+      maskClosable={false}
+      visible
+      onCancel={props.close}
+      onOk={save}
+      width="35vw"
+      permissionCode={'link/Channel/Opcua'}
+      permission={['add', 'edit']}
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} scope={{ useAsyncDataSource, getPolicies, getModes }} />
+      </Form>
+    </Modal>
+  );
+};
+export default SaveChannel;

+ 177 - 0
src/pages/link/Channel/Opcua/savePoint.tsx

@@ -0,0 +1,177 @@
+import { Col, Form, Input, InputNumber, Modal, Row, Select } from 'antd';
+import { useEffect, useState } from 'react';
+// import { service } from '@/pages/link/Channel/Modbus';
+// import { onlyMessage } from '@/utils/util';
+
+interface Props {
+  data: any;
+  close: Function;
+  opcId: string;
+}
+
+const SavePoint = (props: Props) => {
+  const [form] = Form.useForm();
+  const [dataMode, setDataMode] = useState<any>('');
+
+  const handleSave = async () => {
+    const formData = await form.validateFields();
+    console.log(formData);
+  };
+
+  useEffect(() => {
+    console.log(dataMode);
+  }, [dataMode]);
+
+  return (
+    <Modal
+      title={props.data.id ? '编辑点位' : '新增点位'}
+      visible
+      width="40vw"
+      destroyOnClose
+      onOk={handleSave}
+      onCancel={() => {
+        props.close();
+      }}
+    >
+      <Form
+        form={form}
+        layout="vertical"
+        // initialValues={{
+        //     ...props.data,
+        //     codecConfig: {
+        //         ...props.data?.codecConfig,
+        //         readIndex: props.data?.codecConfig?.readIndex || 0,
+        //         scaleFactor: props.data?.codecConfig?.scaleFactor || 1,
+        //         revertBytes: props.data?.codecConfig?.revertBytes || false,
+        //     },
+        // }}
+      >
+        <Row gutter={[24, 24]}>
+          <Col span={24}>
+            <Form.Item
+              label="名称"
+              name="name"
+              required
+              rules={[{ required: true, message: '名称必填' }]}
+            >
+              <Input placeholder="请输入名称" />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={[24, 24]}>
+          <Col span={24}>
+            <Form.Item
+              label="点位ID"
+              name="opcPointId"
+              required
+              rules={[
+                { required: true, message: '点位ID必填' },
+                ({}) => ({
+                  validator(_, value) {
+                    const item = value.substring(0, 2);
+                    if (item === 'i=' || item === 's=' || item === 'g=' || item === 'b=') {
+                      return Promise.resolve();
+                    }
+                    return Promise.reject(new Error('前两个字符必须为i=、s=、g=、b=中的一个'));
+                  },
+                }),
+              ]}
+            >
+              <Input placeholder="请输入点位ID" />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={[24, 24]}>
+          <Col span={12}>
+            <Form.Item
+              label="数据模式"
+              name="dataMode"
+              tooltip={
+                <>
+                  <div>订阅:订阅OPC UA点位数据发生变化后的值</div>
+                  <div>拉取:拉取OPC UA点位数据值</div>
+                  <div>拉取变更值:拉取OPC UA点位数据值,并丢弃处理未发生变化的数据值</div>
+                  <div>
+                    被动读取:不会定时读取OPC UA点位数据值,仅由平台发起读取点位数据命令时返回数据
+                  </div>
+                </>
+              }
+              required
+              rules={[{ required: true, message: '数据模式必选' }]}
+            >
+              <Select
+                placeholder="请选择数据模式"
+                onChange={(value) => {
+                  setDataMode(value);
+                }}
+              >
+                <Select.Option value="sub" key={'sub'}>
+                  订阅
+                </Select.Option>
+                <Select.Option value="pull" key={'pull'}>
+                  拉取
+                </Select.Option>
+                <Select.Option value="pullChange" key={'pullChange'}>
+                  拉取变更值
+                </Select.Option>
+                <Select.Option value="pullPassive" key={'pullPassive'}>
+                  被动读取
+                </Select.Option>
+              </Select>
+            </Form.Item>
+          </Col>
+          {dataMode !== 'pullPassive' && (
+            <Col span={12}>
+              {dataMode === 'sub' && (
+                <Form.Item
+                  label="采集频率"
+                  name="interval"
+                  tooltip="OPC UA服务器采样点位数据发生变化的频率"
+                  rules={[
+                    ({}) => ({
+                      validator(_, value) {
+                        if (value !== 0 || /(^[1-9]\d*$)/.test(value)) {
+                          return Promise.resolve();
+                        }
+                        return Promise.reject(new Error('请输入正整数'));
+                      },
+                    }),
+                  ]}
+                >
+                  <InputNumber
+                    style={{ width: '100%' }}
+                    placeholder="请输入"
+                    addonAfter={<>ms</>}
+                  />
+                </Form.Item>
+              )}
+              {(dataMode === 'pullChange' || dataMode === 'pull') && (
+                <Form.Item
+                  label="拉取频率"
+                  name="interval"
+                  rules={[
+                    ({}) => ({
+                      validator(_, value) {
+                        if (value !== 0 || /(^[1-9]\d*$)/.test(value)) {
+                          return Promise.resolve();
+                        }
+                        return Promise.reject(new Error('请输入正整数'));
+                      },
+                    }),
+                  ]}
+                >
+                  <InputNumber
+                    style={{ width: '100%' }}
+                    placeholder="请输入"
+                    addonAfter={<>ms</>}
+                  />
+                </Form.Item>
+              )}
+            </Col>
+          )}
+        </Row>
+      </Form>
+    </Modal>
+  );
+};
+export default SavePoint;

+ 10 - 0
src/pages/link/Channel/Opcua/service.ts

@@ -3,6 +3,16 @@ import { request } from 'umi';
 import SystemConst from '@/utils/const';
 
 class Service extends BaseService<OpaUa> {
+  saveOpc = (data: any) =>
+    request(`${SystemConst.API_BASE}/opc/client`, {
+      method: 'POST',
+      data,
+    });
+  editOpc = (id: string, data: any) =>
+    request(`${SystemConst.API_BASE}/opc/client/${id}`, {
+      method: 'PUT',
+      data,
+    });
   enable = (id: string) =>
     request(`${SystemConst.API_BASE}/opc/client/${id}/_enable`, {
       method: 'POST',

+ 10 - 7
src/pages/link/Channel/channelCard.tsx

@@ -1,3 +1,4 @@
+import { Ellipsis } from '@/components';
 import { Badge } from 'antd';
 import classNames from 'classnames';
 import { useState } from 'react';
@@ -19,8 +20,8 @@ const ChannelCard = (props: Props) => {
     <div
       className={classNames('channel-card', {
         active: props.active,
-        connect: props.data.status === 'connect',
-        disconnect: props.data.status === 'disconnect',
+        connect: props.data.state.value === 'enabled',
+        disconnect: props.data.state.value === 'disabled',
       })}
       onMouseEnter={() => {
         setActions(true);
@@ -36,15 +37,17 @@ const ChannelCard = (props: Props) => {
         }}
       >
         <div className="card-top-img">
-          {' '}
-          <img src={props.data.status === 'connect' ? connectImg : disconnectImg} />
+          <img src={props.data.state.value === 'enabled' ? connectImg : disconnectImg} />
+        </div>
+
+        <div className="card-top-name">
+          <Ellipsis title={props.data.name} />
         </div>
-        <div className="card-top-name">这里是通道名称</div>
         <div className="card-top-status">
-          {props.data.status === 'connect' ? (
+          {props.data.state.value === 'enabled' ? (
             <Badge status="processing" color={'green'} text={'正常'} />
           ) : (
-            <Badge status="processing" color={'red'} text={'禁用'} />
+            <Badge status="error" color={'red'} text={'禁用'} />
           )}
         </div>
       </div>

+ 9 - 3
src/pages/link/Channel/index.less

@@ -5,23 +5,28 @@
     margin-right: 20px;
     padding-right: 10px;
     border-right: 1px #eee solid;
+
     .item-left-list {
+      width: 230px;
       height: 628px;
       overflow: overlay;
     }
+
     ::-webkit-scrollbar {
       // display:none
-      width: 2px;
+      width: 5px;
       height: 2px;
       scrollbar-arrow-color: #eee;
     }
+
     ::-webkit-scrollbar-thumb {
-      background: rgb(136, 136, 136);
+      background: rgba(148, 144, 144, 0.473);
       border-radius: 5px;
     }
   }
 
   .item-right {
+    width: 100%;
     .ant-card-body {
       padding-top: 0;
       padding-left: 0;
@@ -32,7 +37,7 @@
 .channel-card {
   width: 220px;
   height: 112px;
-  margin-top: 16px;
+  margin-bottom: 16px;
   border: 1px #e0e0e0 solid;
 
   &.active {
@@ -57,6 +62,7 @@
     .card-top-name {
       position: relative;
       bottom: 8px;
+      width: calc(100% - 66px);
       margin-left: 16px;
       color: #323130;
       font-weight: 600;

+ 0 - 372
src/pages/link/Channel/new.tsx

@@ -1,372 +0,0 @@
-import { Badge, Button, Card, Divider, Dropdown, Input, Menu } from 'antd';
-import { useDomFullHeight } from '@/hooks';
-import './index.less';
-import SearchComponent from '@/components/SearchComponent';
-import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
-import PermissionButton from '@/components/PermissionButton';
-import {
-  DeleteOutlined,
-  EditOutlined,
-  ExportOutlined,
-  ImportOutlined,
-  PlayCircleOutlined,
-  PlusOutlined,
-  StopOutlined,
-} from '@ant-design/icons';
-import { useRef, useState } from 'react';
-import { useIntl } from 'umi';
-import ChannelCard from './channelCard';
-
-const NewModbus = () => {
-  const { minHeight } = useDomFullHeight(`.modbus`);
-  const intl = useIntl();
-  const actionRef = useRef<ActionType>();
-  const { permission } = PermissionButton.usePermission('link/Channel/Modbus');
-  const [param, setParam] = useState({});
-  const [activeKey, setActiveKey] = useState<any>('');
-  const data = [
-    {
-      id: 1,
-      status: 'connect',
-      state: {
-        text: '正常',
-        value: 'enabled',
-      },
-    },
-    {
-      id: 2,
-      status: 'disconnect',
-      state: {
-        text: '禁用',
-        value: 'disabled',
-      },
-    },
-  ];
-
-  const columns: ProColumns<any>[] = [
-    {
-      title: '名称',
-      dataIndex: 'name',
-      ellipsis: true,
-      width: 200,
-      fixed: 'left',
-    },
-    {
-      title: '功能码',
-      dataIndex: 'host',
-    },
-    {
-      title: '从站ID',
-      dataIndex: 'port',
-      search: false,
-      valueType: 'digit',
-    },
-    {
-      title: '寄存器数量',
-      dataIndex: 'port',
-      search: false,
-      valueType: 'digit',
-    },
-    {
-      title: '地址',
-      dataIndex: 'port',
-      search: false,
-      valueType: 'digit',
-    },
-    {
-      title: '当前数据',
-      dataIndex: 'port',
-      search: false,
-      valueType: 'digit',
-    },
-    {
-      title: '采集状态',
-      dataIndex: 'port',
-      search: false,
-      valueType: 'digit',
-    },
-    {
-      title: '状态',
-      dataIndex: 'state',
-      renderText: (state) => (
-        <Badge text={state?.text} status={state?.value === 'disabled' ? 'error' : 'success'} />
-      ),
-      valueType: 'select',
-      valueEnum: {
-        disabled: {
-          text: intl.formatMessage({
-            id: 'pages.data.option.disabled',
-            defaultMessage: '禁用',
-          }),
-          status: 'disabled',
-        },
-        enabled: {
-          text: '正常',
-          status: 'enabled',
-        },
-      },
-      filterMultiple: false,
-    },
-    {
-      title: '操作',
-      valueType: 'option',
-      align: 'center',
-      width: 120,
-      fixed: 'right',
-      render: (text, record) => [
-        <PermissionButton
-          isPermission={permission.update}
-          key="edit"
-          onClick={() => {
-            // setVisible(true);
-            // setCurrent(record);
-          }}
-          type={'link'}
-          style={{ padding: 0 }}
-          tooltip={{
-            title: intl.formatMessage({
-              id: 'pages.data.option.edit',
-              defaultMessage: '编辑',
-            }),
-          }}
-        >
-          <EditOutlined />
-        </PermissionButton>,
-        <PermissionButton
-          type="link"
-          key={'action'}
-          style={{ padding: 0 }}
-          popConfirm={{
-            title: intl.formatMessage({
-              id: `pages.data.option.${
-                record.state.value !== 'disabled' ? 'disabled' : 'enabled'
-              }.tips`,
-              defaultMessage: '确认禁用?',
-            }),
-            onConfirm: async () => {
-              //   if (record.state.value === 'disabled') {
-              //     await service.edit({
-              //       ...record,
-              //       state: 'enabled',
-              //     });
-              //   } else {
-              //     await service.edit({
-              //       ...record,
-              //       state: 'disabled',
-              //     });
-              //   }
-              //   onlyMessage(
-              //     intl.formatMessage({
-              //       id: 'pages.data.option.success',
-              //       defaultMessage: '操作成功!',
-              //     }),
-              //   );
-              //   actionRef.current?.reload();
-            },
-          }}
-          isPermission={permission.action}
-          tooltip={{
-            title: intl.formatMessage({
-              id: `pages.data.option.${record.state.value !== 'disabled' ? 'disabled' : 'enabled'}`,
-              defaultMessage: record.state.value !== 'disabled' ? '禁用' : '启用',
-            }),
-          }}
-        >
-          {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
-        </PermissionButton>,
-        <PermissionButton
-          isPermission={permission.delete}
-          style={{ padding: 0 }}
-          disabled={record.state.value === 'enabled'}
-          popConfirm={{
-            title: '确认删除',
-            disabled: record.state.value === 'enabled',
-            onConfirm: async () => {
-              //   const resp: any = await service.remove(record.id);
-              //   if (resp.status === 200) {
-              //     onlyMessage(
-              //       intl.formatMessage({
-              //         id: 'pages.data.option.success',
-              //         defaultMessage: '操作成功!',
-              //       }),
-              //     );
-              //     actionRef.current?.reload();
-              //   }
-            },
-          }}
-          key="delete"
-          type="link"
-        >
-          <DeleteOutlined />
-        </PermissionButton>,
-      ],
-    },
-  ];
-
-  const menu = (
-    <Menu>
-      <Menu.Item key="1">
-        <PermissionButton
-          isPermission={permission.export}
-          icon={<ExportOutlined />}
-          type="default"
-          onClick={() => {
-            // setExportVisible(true);
-          }}
-        >
-          批量导出设备
-        </PermissionButton>
-      </Menu.Item>
-      <Menu.Item key="2">
-        <PermissionButton
-          isPermission={permission.import}
-          icon={<ImportOutlined />}
-          onClick={() => {
-            // setImportVisible(true);
-          }}
-        >
-          批量导入设备
-        </PermissionButton>
-      </Menu.Item>
-    </Menu>
-  );
-
-  return (
-    <Card className="modbus" style={{ minHeight }}>
-      <div className="item">
-        <div className="item-left">
-          <div style={{ width: 220 }}>
-            <Input.Search
-              placeholder="请输入名称"
-              allowClear
-              onSearch={(value) => {
-                console.log(value);
-              }}
-            />
-            <PermissionButton
-              onClick={() => {
-                // setDeviceVisiable(true);
-              }}
-              isPermission={permission.add}
-              key="add"
-              icon={<PlusOutlined />}
-              type="default"
-              style={{ width: '100%', marginTop: 16 }}
-            >
-              新增
-            </PermissionButton>
-            <div className="item-left-list">
-              {data.map((item) => (
-                <ChannelCard
-                  active={activeKey === item.id}
-                  data={item}
-                  onClick={() => {
-                    setActiveKey(item.id);
-                  }}
-                  actions={
-                    <>
-                      <PermissionButton
-                        isPermission={permission.update}
-                        key="edit"
-                        onClick={() => {
-                          // setVisible(true);
-                          // setCurrent(record);
-                        }}
-                        type={'link'}
-                        style={{ padding: 0 }}
-                      >
-                        <EditOutlined />
-                        编辑
-                      </PermissionButton>
-                      <Divider type="vertical" />
-                      <PermissionButton
-                        isPermission={permission.update}
-                        key="enbale"
-                        type={'link'}
-                        style={{ padding: 0 }}
-                        popConfirm={{
-                          title: intl.formatMessage({
-                            id: `pages.data.option.${
-                              item.state.value !== 'disabled' ? 'disabled' : 'enabled'
-                            }.tips`,
-                            defaultMessage: '确认禁用?',
-                          }),
-                          onConfirm: async () => {},
-                        }}
-                      >
-                        {item.state.value === 'enabled' ? <StopOutlined /> : <PlayCircleOutlined />}
-                        {item.state.value === 'enabled' ? '禁用' : '启用'}
-                      </PermissionButton>
-                      <Divider type="vertical" />
-                      <PermissionButton
-                        isPermission={permission.delete}
-                        style={{ padding: 0 }}
-                        disabled={item.state.value === 'enabled'}
-                        popConfirm={{
-                          title: '确认删除',
-                          disabled: item.state.value === 'enabled',
-                          onConfirm: async () => {},
-                        }}
-                        key="delete"
-                        type="link"
-                      >
-                        <DeleteOutlined />
-                      </PermissionButton>
-                    </>
-                  }
-                />
-              ))}
-            </div>
-          </div>
-        </div>
-        <div className="item-right">
-          <SearchComponent<any>
-            field={columns}
-            target="modbus"
-            onSearch={(parms) => {
-              actionRef.current?.reset?.();
-              setParam(parms);
-            }}
-          />
-          <ProTable
-            actionRef={actionRef}
-            params={param}
-            columns={columns}
-            rowKey="id"
-            // scroll={{ x: 1000 }}
-            search={false}
-            columnEmptyText={''}
-            headerTitle={
-              <>
-                <PermissionButton
-                  onClick={() => {
-                    // setMode('add');
-                    // setVisible(true);
-                    // setCurrent({});
-                  }}
-                  isPermission={permission.add}
-                  key="add"
-                  icon={<PlusOutlined />}
-                  type="primary"
-                  style={{ marginRight: 10 }}
-                >
-                  {intl.formatMessage({
-                    id: 'pages.data.option.add',
-                    defaultMessage: '新增',
-                  })}
-                </PermissionButton>
-                <Dropdown key={'more'} overlay={menu} placement="bottom">
-                  <Button>批量操作</Button>
-                </Dropdown>
-              </>
-            }
-            // request={async (params) =>
-            //     service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
-            // }
-          />
-        </div>
-      </div>
-    </Card>
-  );
-};
-export default NewModbus;

+ 16 - 11
src/pages/media/Cascade/Channel/index.tsx

@@ -4,7 +4,7 @@ import { CloseOutlined, DisconnectOutlined, EditOutlined } from '@ant-design/ico
 import { PageContainer } from '@ant-design/pro-layout';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { Button, Input, Popover, Space } from 'antd';
+import { Button, Input, message, Popover, Space } from 'antd';
 import { useRef, useState } from 'react';
 import { useIntl, useLocation } from 'umi';
 import BindChannel from './BindChannel';
@@ -12,6 +12,7 @@ import BadgeStatus, { StatusColorEnum } from '@/components/BadgeStatus';
 import { PermissionButton } from '@/components';
 import { useDomFullHeight } from '@/hooks';
 import { onlyMessage } from '@/utils/util';
+import { Ellipsis } from '@/components';
 
 const Channel = () => {
   const location: any = useLocation();
@@ -55,13 +56,17 @@ const Channel = () => {
           style={{ marginTop: 10, width: '100%' }}
           onClick={async () => {
             if (!!data) {
-              const resp: any = await service.editBindInfo(record.id, {
-                gbChannelId: data,
-              });
-              if (resp.status === 200) {
-                onlyMessage('操作成功');
-                actionRef.current?.reload();
-                setPopvisible('');
+              if (data.length <= 64) {
+                const resp: any = await service.editBindInfo(record.id, {
+                  gbChannelId: data,
+                });
+                if (resp.status === 200) {
+                  onlyMessage('操作成功');
+                  actionRef.current?.reload();
+                  setPopvisible('');
+                }
+              } else {
+                message.error('最多可输入64个字符');
               }
             } else {
               onlyMessage('请输入国标ID', 'error');
@@ -92,8 +97,8 @@ const Channel = () => {
       // ellipsis:true,
       tooltip: '国标级联有18位、20位两种格式。在当前页面修改不会修改视频设备-通道页面中的国标ID',
       render: (text: any, record: any) => (
-        <span>
-          {text}
+        <div style={{ display: 'flex' }}>
+          <Ellipsis title={text || ''} />
           <Popover
             visible={popVisible === record.id}
             trigger="click"
@@ -127,7 +132,7 @@ const Channel = () => {
               <EditOutlined />
             </a>
           </Popover>
-        </span>
+        </div>
       ),
     },
     {

+ 1 - 1
src/pages/media/Cascade/Save/index.tsx

@@ -429,7 +429,7 @@ const Save = () => {
               <div>需与上级平台设置的心跳周期保持一致,通常默认60秒。</div>
               <h2>10、注册间隔</h2>
               <div>
-                若SIP代理通过注册方式校时,其注册过期时间宜设置为小于 SIP代理与 SIP服务器出现1s误
+                若SIP代理通过注册方式校时,其注册间隔时间宜设置为小于 SIP代理与 SIP服务器出现1s误
                 差所经过的运行时间。
               </div>
             </div>

+ 13 - 9
src/pages/notice/Config/Debug/index.tsx

@@ -218,15 +218,19 @@ const Debug = observer(() => {
     // const list = Store.get('notice-template-list');
     // const _template = list.find((item: any) => item.id === templateId);
 
-    const resp = await service.debug(state?.current.id, templateId, data.variableDefinitions?.reduce(
-      (previousValue: any, currentValue: { id: any; value: any }) => {
-        return {
-          ...previousValue,
-          [currentValue.id]: currentValue.value,
-        };
-      },
-      {},
-    ));
+    const resp = await service.debug(
+      state?.current.id,
+      templateId,
+      data.variableDefinitions?.reduce(
+        (previousValue: any, currentValue: { id: any; value: any }) => {
+          return {
+            ...previousValue,
+            [currentValue.id]: currentValue.value,
+          };
+        },
+        {},
+      ),
+    );
     if (resp.status === 200) {
       onlyMessage('操作成功!');
       state.debug = false;

+ 3 - 3
src/pages/notice/Config/Detail/index.tsx

@@ -304,7 +304,7 @@ const Detail = observer(() => {
             'x-visible': id === 'voice' || id === 'sms',
             properties: {
               regionId: {
-                title: 'regionId',
+                title: 'RegionId',
                 required: true,
                 'x-component-props': {
                   placeholder: '请输入regionId',
@@ -313,7 +313,7 @@ const Detail = observer(() => {
                 'x-decorator': 'FormItem',
               },
               accessKeyId: {
-                title: 'accessKeyId',
+                title: 'AccessKeyId',
                 required: true,
                 'x-component-props': {
                   placeholder: '请输入accessKeyId',
@@ -322,7 +322,7 @@ const Detail = observer(() => {
                 'x-decorator': 'FormItem',
               },
               secret: {
-                title: 'secret',
+                title: 'Secret',
                 required: true,
                 'x-component-props': {
                   placeholder: '请输入secret',

+ 38 - 12
src/pages/system/Menu/Setting/baseMenu.ts

@@ -608,12 +608,32 @@ export default [
                 icon: 'icon-changjingliandong',
                 permissions: [],
                 buttons: [
-                  {id: "update", name: "编辑", permissions: [{permission: "modbus-master", actions: ["query", "save"]}]},
-                  {id: "action", name: "启/禁用", permissions: [{permission: "modbus-master", actions: ["query", "save"]}]},
-                  {id: "view", name: "设备接入", permissions: [{permission: "modbus-master", actions: ["query", "save"]}]},
-                  {id: "delete", name: "删除", permissions: [{permission: "modbus-master", actions: ["query", "delete"]}]},
-                  {id: "add", name: "新增", permissions: [{permission: "modbus-master", actions: ["query", "save"]}]},
-                ]
+                  {
+                    id: 'update',
+                    name: '编辑',
+                    permissions: [{ permission: 'modbus-master', actions: ['query', 'save'] }],
+                  },
+                  {
+                    id: 'action',
+                    name: '启/禁用',
+                    permissions: [{ permission: 'modbus-master', actions: ['query', 'save'] }],
+                  },
+                  {
+                    id: 'view',
+                    name: '设备接入',
+                    permissions: [{ permission: 'modbus-master', actions: ['query', 'save'] }],
+                  },
+                  {
+                    id: 'delete',
+                    name: '删除',
+                    permissions: [{ permission: 'modbus-master', actions: ['query', 'delete'] }],
+                  },
+                  {
+                    id: 'add',
+                    name: '新增',
+                    permissions: [{ permission: 'modbus-master', actions: ['query', 'save'] }],
+                  },
+                ],
               },
             ],
           },
@@ -626,10 +646,13 @@ export default [
             url: '/iot/link/firmware',
             icon: 'icon-wangluozujian',
             permissions: [
-              {permission: "firmware-manager", actions: ["query", "save", "delete"]},
-              {permission: "firmware-upgrade-task-manager", actions: ["query", "save", "delete", "deploy"]},
-              {permission: "device-product", actions: ["query"]},
-              {permission: "device-api", actions: ["query"]},
+              { permission: 'firmware-manager', actions: ['query', 'save', 'delete'] },
+              {
+                permission: 'firmware-upgrade-task-manager',
+                actions: ['query', 'save', 'delete', 'deploy'],
+              },
+              { permission: 'device-product', actions: ['query'] },
+              { permission: 'device-api', actions: ['query'] },
             ],
             buttons: [
               {
@@ -654,7 +677,6 @@ export default [
               },
             ],
           },
-
         ],
       },
 
@@ -1361,7 +1383,11 @@ export default [
         ],
         buttons: [
           { id: 'view', name: '查看', permissions: [{ permission: 'menu', actions: ['query'] }] },
-          { id: 'setting', name: '配置', permissions: [{ permission: 'menu', actions: ['query', 'save', 'grant'] }] },
+          {
+            id: 'setting',
+            name: '配置',
+            permissions: [{ permission: 'menu', actions: ['query', 'save', 'grant'] }],
+          },
           {
             id: 'update',
             name: '编辑',

+ 1 - 1
src/pages/system/Platforms/save.tsx

@@ -39,7 +39,7 @@ export default (props: SaveProps) => {
   const [loading, setLoading] = useState(false);
   const { permission: RolePermission } = usePermissions('system/Role');
 
-  console.log(RolePermission)
+  console.log(RolePermission);
 
   const SchemaField = createSchemaField({
     components: {

+ 10 - 6
src/pages/user/Login/index.tsx

@@ -33,16 +33,20 @@ const Login: React.FC = () => {
         ...initialState,
         currentUser: userInfo,
       });
-      return userInfo
+      return userInfo;
     }
-    return null
+    return null;
   };
 
   const loginRef = useRef<Partial<LoginParam>>({});
-  const loginForm = useMemo(() => createForm({
-    validateFirst: true,
-    initialValues: loginRef.current,
-  }), [captcha]);
+  const loginForm = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+        initialValues: loginRef.current,
+      }),
+    [captcha],
+  );
 
   const [loading, setLoading] = useState<boolean>(false);
 

+ 1 - 1
src/pages/user/Login/service.ts

@@ -64,7 +64,7 @@ const Service = {
     request(`/${SystemConst.API_BASE}/user/detail`, {
       method: 'GET',
     }),
-  initPage: () => request(`/${SystemConst.API_BASE}/user/settings/init`, { method: 'GET' })
+  initPage: () => request(`/${SystemConst.API_BASE}/user/settings/init`, { method: 'GET' }),
 };
 
 export default Service;