100011797 пре 3 година
родитељ
комит
1fc72f7f9c
37 измењених фајлова са 2687 додато и 2136 уклоњено
  1. 1 1
      src/app.tsx
  2. 0 303
      src/pages/device/Instance/Detail/Modbus/channelList.tsx
  3. 0 70
      src/pages/device/Instance/Detail/Modbus/editTable.tsx
  4. 1 0
      src/pages/device/Instance/Detail/Modbus/index.less
  5. 510 393
      src/pages/device/Instance/Detail/Modbus/index.tsx
  6. 1 3
      src/pages/device/Instance/Detail/index.tsx
  7. 45 5
      src/pages/init-home/components/basis.tsx
  8. 231 550
      src/pages/init-home/components/data/RoleData.ts
  9. 64 68
      src/pages/init-home/components/data/index.tsx
  10. 1 0
      src/pages/init-home/components/data/save/index.tsx
  11. 9 8
      src/pages/init-home/components/menu.tsx
  12. 68 9
      src/pages/init-home/components/role.tsx
  13. 75 16
      src/pages/init-home/index.tsx
  14. 24 0
      src/pages/init-home/service.ts
  15. 121 0
      src/pages/link/Channel/Modbus/Export/index.tsx
  16. 225 0
      src/pages/link/Channel/Modbus/import/index.tsx
  17. 267 100
      src/pages/link/Channel/Modbus/index.tsx
  18. 16 2
      src/pages/link/Channel/Modbus/saveChannel.tsx
  19. 32 12
      src/pages/link/Channel/Modbus/savePoint.tsx
  20. 63 30
      src/pages/link/Channel/Modbus/service.ts
  21. 387 157
      src/pages/link/Channel/Opcua/index.tsx
  22. 250 0
      src/pages/link/Channel/Opcua/saveChannel.tsx
  23. 177 0
      src/pages/link/Channel/Opcua/savePoint.tsx
  24. 10 0
      src/pages/link/Channel/Opcua/service.ts
  25. 10 7
      src/pages/link/Channel/channelCard.tsx
  26. 8 3
      src/pages/link/Channel/index.less
  27. 0 372
      src/pages/link/Channel/new.tsx
  28. 8 6
      src/pages/notice/Config/Debug/index.tsx
  29. 3 3
      src/pages/system/Department/Assets/product/bind.tsx
  30. 3 3
      src/pages/system/Department/Assets/product/index.tsx
  31. 3 3
      src/pages/system/Department/Member/bind.tsx
  32. 3 3
      src/pages/system/Department/Member/index.tsx
  33. 44 0
      src/pages/system/Menu/Setting/baseMenu.ts
  34. 1 0
      src/pages/system/Menu/Setting/index.tsx
  35. 4 2
      src/pages/system/Platforms/save.tsx
  36. 21 7
      src/pages/user/Login/index.tsx
  37. 1 0
      src/pages/user/Login/service.ts

+ 1 - 1
src/app.tsx

@@ -343,7 +343,7 @@ export function patchRoutes(routes: any) {
 }
 
 export function render(oldRender: any) {
-  if (history.location.pathname !== loginPath && history.location.pathname !== bindPath) {
+  if (![loginPath, bindPath].includes(history.location.pathname)) {
     // SystemConfigService.getAMapKey().then((res) => {
     //   if (res && res.status === 200 && res.result) {
     //     localStorage.setItem(SystemConst.AMAP_KEY, res.result.apiKey);

+ 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 {

+ 510 - 393
src/pages/device/Instance/Detail/Modbus/index.tsx

@@ -1,425 +1,542 @@
+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, 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 { 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) {
+        console.log(res.result);
+        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',
+    }));
+    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);
+      }
+    });
+  }, [reload]);
 
-  return (
-    <Card className={styles.list} style={{ minHeight }}>
-      <div style={{ display: 'flex' }}>
-        <div>
-          <div style={{ width: '250px', marginTop: 15 }}>
-            <Input.Search
-              placeholder="请输入通道名称"
-              allowClear
-              onSearch={(value) => {
-                if (value) {
-                  const items = bindList.filter((item: any) => item.name.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: '',
-                    });
-                  }
-                }
-              }}
-            />
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Editable,
+      ArrayTable,
+      Select,
+      NumberPicker,
+      Render,
+      ActionButton,
+      StatusRender,
+    },
+  });
 
-            <PermissionButton
-              onClick={() => {
-                setVisible(true);
-                setChannel({});
-              }}
-              isPermission={permission.add}
-              key="add"
-              icon={<PlusOutlined />}
-              type="dashed"
-              style={{ width: '100%', margin: '16px 0 18px 0' }}
-            >
-              新增通道
-            </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,
+  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,
                   },
-                  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,
+                  enum: masterList,
                 },
-                status: res.status,
-              };
-            }}
-          />
-        </div>
-      </div>
+              },
+            },
+            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)}}'],
+                },
+              },
+            },
 
-      {visible && (
-        <Save
-          data={channel}
-          close={() => {
-            setVisible(false);
+            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="modbus" style={{ minHeight }}>
+      <div className="edit-top">
+        <Input.Search
+          placeholder="请输入属性"
+          allowClear
+          style={{ width: 190 }}
+          onSearch={(value) => {
+            console.log(value);
+            if (value) {
+              const items = properties.filter((item: any) => item.metadataId.match(value));
+              setFilterList(items);
+            } else {
+              setFilterList(properties);
+            }
           }}
-          device={InstanceModel.detail}
         />
-      )}
-      {pointVisiable && (
-        <AddPoint
-          deviceId={deviceId}
-          opcUaId={opcId}
-          data={current}
-          close={() => {
-            setPointVisiable(false);
-            actionRef.current?.reload();
+        <PermissionButton
+          onClick={async () => {
+            const value: any = await form.submit();
+            const items = value.array.filter((item: any) => item.collectorId);
+            save(items);
           }}
-        />
-      )}
+          isPermission={permission.add}
+          key="add"
+          type="primary"
+          style={{ marginRight: 10 }}
+        >
+          保存
+        </PermissionButton>
+      </div>
+      <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') {

+ 45 - 5
src/pages/init-home/components/basis.tsx

@@ -1,16 +1,56 @@
 import { UploadImage } from '@/components';
 import { Col, Form, Input, Row, Select } from 'antd';
-import { useEffect } from 'react';
+import { useEffect, forwardRef, useImperativeHandle } from 'react';
+import { service } from '../index';
 
 interface Props {
-  getData: Function;
+  getData?: Function;
 }
 
-const Basis = (props: Props) => {
+const Basis = forwardRef((props: Props, ref) => {
   const [form] = Form.useForm();
 
+  const saveData = () => {
+    return new Promise(async (resolve) => {
+      const formData = await form.validateFields().catch(() => {
+        resolve(false);
+      });
+      if (formData) {
+        const item = [
+          {
+            scope: 'basis',
+            properties: {
+              ...formData,
+              apikey: '',
+            },
+          },
+          {
+            scope: 'api',
+            properties: {
+              api: formData.apikey,
+            },
+          },
+        ];
+        const res = await service.save(item);
+        if (res.status === 200) {
+          resolve(true);
+        } else {
+          resolve(false);
+        }
+      } else {
+        resolve(false);
+      }
+    });
+  };
+
+  useImperativeHandle(ref, () => ({
+    save: saveData,
+  }));
+
   useEffect(() => {
-    props.getData(form);
+    if (props.getData) {
+      props.getData(form);
+    }
   }, []);
 
   return (
@@ -84,5 +124,5 @@ const Basis = (props: Props) => {
       </Row>
     </Form>
   );
-};
+});
 export default Basis;

Разлика између датотеке није приказан због своје велике величине
+ 231 - 550
src/pages/init-home/components/data/RoleData.ts


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

@@ -1,81 +1,77 @@
-import { useEffect, useState } from 'react';
+import { forwardRef, useImperativeHandle, useState } from 'react';
 import { service } from '../../index';
 import Save from './save';
-interface Props {
-  isTrigger: boolean;
-  onChange: (state: boolean) => void;
-}
 
-const Data = (props: Props) => {
+const Data = forwardRef((_, ref) => {
   const [flag, setFlag] = useState<boolean>(false);
   const [visible, setVisible] = useState<boolean>(false);
   const [values, setValues] = useState<any>({});
 
-  const handleChange = async () => {
-    if (Object.keys(values).length === 0) {
-      props.onChange(true);
-      return;
-    }
-    // 新增网络组件
-    const network = await service.saveNetwork({
-      type: 'MQTT_SERVER',
-      shareCluster: true,
-      name: 'MQTT网络组件',
-      configuration: {
-        host: '0.0.0.0',
-        secure: false,
-        port: values.port,
-        publicHost: values.publicHost,
-        publicPort: values.publicPort,
-      },
-    });
-    // 保存协议
-    const protocol = await service.saveProtocol();
-    let protocolItem: any = undefined;
-    if (protocol.status === 200) {
-      const proid = await service.getProtocol();
-      if (proid.status === 200) {
-        protocolItem = (proid?.result || []).find((it: any) => it.name === 'JetLinks官方协议');
+  const handleChange = () => {
+    return new Promise(async (resolve) => {
+      if (!Object.keys(values).length) {
+        return resolve(true);
+      }
+      try {
+        // 新增网络组件
+        const network = await service.saveNetwork({
+          type: 'MQTT_SERVER',
+          shareCluster: true,
+          name: 'MQTT网络组件',
+          configuration: {
+            host: '0.0.0.0',
+            secure: false,
+            port: values.port,
+            publicHost: values.publicHost,
+            publicPort: values.publicPort,
+          },
+        });
+        // 保存协议
+        const protocol = await service.saveProtocol();
+        let protocolItem: any = undefined;
+        if (protocol.status === 200) {
+          const proid = await service.getProtocol();
+          if (proid.status === 200) {
+            protocolItem = (proid?.result || []).find((it: any) => it.name === 'JetLinks官方协议');
+          }
+        }
+        // 新增设备接入网关
+        const accessConfig = await service.saveAccessConfig({
+          name: 'MQTT类型设备接入网关',
+          provider: 'mqtt-server-gateway',
+          protocol: protocolItem?.id,
+          transport: 'MQTT',
+          channel: 'network',
+          channelId: network?.result?.id,
+        });
+        // 新增产品
+        const product = await service.saveProduct({
+          name: 'MQTT产品',
+          messageProtocol: protocolItem?.id,
+          protocolName: protocolItem?.name,
+          transportProtocol: 'MQTT',
+          deviceType: 'device',
+          accessId: accessConfig.result?.id,
+          accessName: accessConfig.result?.name,
+          accessProvider: 'mqtt-server-gateway',
+        });
+        // 新增设备
+        const device = await service.saveDevice({
+          name: 'MQTT设备',
+          productId: product?.result?.id,
+          productName: product?.result?.name,
+        });
+        resolve(device.status === 200);
+      } catch (e) {
+        console.log(e);
+        resolve(false);
       }
-    }
-    // 新增设备接入网关
-    const accessConfig = await service.saveAccessConfig({
-      name: 'MQTT类型设备接入网关',
-      provider: 'mqtt-server-gateway',
-      protocol: protocolItem?.id,
-      transport: 'MQTT',
-      channel: 'network',
-      channelId: network?.result?.id,
-    });
-    // 新增产品
-    const product = await service.saveProduct({
-      name: 'MQTT产品',
-      messageProtocol: protocolItem?.id,
-      protocolName: protocolItem?.name,
-      transportProtocol: 'MQTT',
-      deviceType: 'device',
-      accessId: accessConfig.result?.id,
-      accessName: accessConfig.result?.name,
-      accessProvider: 'mqtt-server-gateway',
-    });
-    // 新增设备
-    const device = await service.saveDevice({
-      name: 'MQTT设备',
-      productId: product?.result?.id,
-      productName: product?.result?.name,
     });
-    if (device.status === 200) {
-      props.onChange(true);
-    } else {
-      props.onChange(false);
-    }
   };
 
-  useEffect(() => {
-    if (props.isTrigger) {
-      handleChange();
-    }
-  }, [props.isTrigger]);
+  useImperativeHandle(ref, () => ({
+    save: handleChange,
+  }));
 
   return (
     <div>
@@ -105,6 +101,6 @@ const Data = (props: Props) => {
       )}
     </div>
   );
-};
+});
 
 export default Data;

+ 1 - 0
src/pages/init-home/components/data/save/index.tsx

@@ -171,4 +171,5 @@ const Save = (props: Props) => {
     </Modal>
   );
 };
+
 export default Save;

+ 9 - 8
src/pages/init-home/components/menu.tsx

@@ -1,13 +1,8 @@
 import { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
 import BaseMenu from '@/pages/system/Menu/Setting/baseMenu';
 import { service } from '../index';
-import { observable } from '@formily/reactive';
 
-export const MenuDataModel = observable<{ menuData: any[] }>({
-  menuData: [],
-});
-
-const Menu = forwardRef((_, ref) => {
+const Menu = forwardRef((props: { onChange?: (menu: any) => void }, ref) => {
   const [count, setCount] = useState(0);
   const menuRef = useRef<any[]>();
 
@@ -53,14 +48,20 @@ const Menu = forwardRef((_, ref) => {
       );
       const _count = menuCount(newTree);
       menuRef.current = newTree;
-      MenuDataModel.menuData = newTree;
       setCount(_count);
     }
   };
 
   useImperativeHandle(ref, () => ({
     save: () => {
-      console.log(menuRef.current);
+      return new Promise((resolve) => {
+        if (props.onChange) {
+          props.onChange(menuRef.current);
+        }
+        service.updateMenus(menuRef.current).then((res) => {
+          resolve(res.status === 200);
+        });
+      });
     },
   }));
 

+ 68 - 9
src/pages/init-home/components/role.tsx

@@ -1,18 +1,77 @@
 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<string[]>([]);
+  const [keys, setKeys] = useState<roleKeysType[]>([]);
+
+  const findMenuByRole = (menu: any[], code: string): any => {
+    let _item = null;
+    menu.some((item) => {
+      if (item.code === code) {
+        _item = item;
+        return true;
+      }
+
+      if (item.children) {
+        const childrenItem = findMenuByRole(item.children, code);
+        if (childrenItem) {
+          _item = childrenItem;
+          return true;
+        }
+        return false;
+      }
+
+      return null;
+    });
+    return _item;
+  };
 
   useImperativeHandle(
     ref,
     () => ({
-      save: () => {
-        console.log(keys);
-        // 获取当前选中的角色
+      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;
+                }
+                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],
@@ -22,7 +81,7 @@ const Role = forwardRef((_, ref) => {
     <div className={'init-home-role'}>
       <Checkbox.Group
         onChange={(e) => {
-          setKeys(e as string[]);
+          setKeys(e as roleKeysType[]);
         }}
       >
         <div className={'init-home-role-content'}>
@@ -30,7 +89,7 @@ const Role = forwardRef((_, ref) => {
             className={classNames('role-item role-image-1', { active: keys.includes('device') })}
           >
             <div className={'role-item-title'}>
-              <Checkbox value={'device'}></Checkbox>
+              <Checkbox value={ROLEKEYS.device}></Checkbox>
               <div>设备接入岗</div>
             </div>
             <div className={'role-item-content'}></div>
@@ -38,7 +97,7 @@ const Role = forwardRef((_, ref) => {
           </div>
           <div className={classNames('role-item role-image-2', { active: keys.includes('link') })}>
             <div className={'role-item-title'}>
-              <Checkbox value={'link'}></Checkbox>
+              <Checkbox value={ROLEKEYS.link}></Checkbox>
               <div>运维管理岗</div>
             </div>
             <div className={'role-item-content'}></div>
@@ -48,7 +107,7 @@ const Role = forwardRef((_, ref) => {
             className={classNames('role-item role-image-3', { active: keys.includes('complex') })}
           >
             <div className={'role-item-title'}>
-              <Checkbox value={'complex'}></Checkbox>
+              <Checkbox value={ROLEKEYS.complex}></Checkbox>
               <div>综合管理岗</div>
             </div>
             <div className={'role-item-content'}></div>

+ 75 - 16
src/pages/init-home/index.tsx

@@ -1,32 +1,54 @@
 import { TitleComponent } from '@/components';
-import { Button, Collapse, Spin, Steps } 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 { useState } from 'react';
+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 [current, setCurrent] = useState<number>(0);
+  const [, setCurrent] = useState<number>(0);
+  const history = useHistory();
+
+  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 jump = () => {
+    history.push(BaseMenu[0].url);
+  };
+
+  useEffect(() => {
+    service.getInit().then((res) => {
+      if (res.status === 200 && res.result.length) {
+        // jump()
+      }
+    });
+  }, []);
 
   return (
     <div className={styles.init}>
       <TitleComponent data={'系统初始化'} />
       <div className={styles.box}>
         <div className={styles.container}>
-          <div className={styles.left}>
-            <Steps direction="vertical" current={current} percent={60} style={{ height: '100%' }}>
-              <Steps.Step />
-              <Steps.Step />
-              <Steps.Step />
-              <Steps.Step />
-            </Steps>
-          </div>
+          {/*<div className={styles.left}>*/}
+          {/*  <Steps direction="vertical" current={current} percent={60} style={{ height: '100%' }}>*/}
+          {/*    <Steps.Step />*/}
+          {/*    <Steps.Step />*/}
+          {/*    <Steps.Step />*/}
+          {/*    <Steps.Step />*/}
+          {/*  </Steps>*/}
+          {/*</div>*/}
           <div className={styles.right}>
             <Spin spinning={loadings}>
               <Collapse defaultActiveKey={['1', '2', '3', '4']}>
@@ -41,7 +63,7 @@ const InitHome = () => {
                   }
                   key="1"
                 >
-                  <Basis getData={() => {}} />
+                  <Basis ref={baseRef} />
                 </Collapse.Panel>
                 <Collapse.Panel
                   header={
@@ -51,7 +73,7 @@ const InitHome = () => {
                   }
                   key="2"
                 >
-                  <Menu />
+                  <Menu ref={menuRef} />
                 </Collapse.Panel>
                 <Collapse.Panel
                   header={
@@ -61,7 +83,7 @@ const InitHome = () => {
                   }
                   key="3"
                 >
-                  <Role />
+                  <Role ref={roleRef} />
                 </Collapse.Panel>
                 <Collapse.Panel
                   header={
@@ -71,7 +93,7 @@ const InitHome = () => {
                   }
                   key="4"
                 >
-                  <Data isTrigger={false} onChange={() => {}} />
+                  <Data ref={dataRef} />
                 </Collapse.Panel>
               </Collapse>
             </Spin>
@@ -79,9 +101,46 @@ const InitHome = () => {
               type="primary"
               style={{ marginTop: 20 }}
               loading={loadings}
-              onClick={() => {
+              onClick={async () => {
                 setLoadings(true);
                 setCurrent(0);
+                if (!cacheRef.current?.has('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');
+                }
+
+                if (!cacheRef.current?.has('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');
+                }
+                //  记录当前
+                service.saveInit().then((res) => {
+                  if (res.status === 200) {
+                    jump();
+                  }
+                });
               }}
             >
               确认

+ 24 - 0
src/pages/init-home/service.ts

@@ -47,6 +47,30 @@ class Service extends BaseService<any> {
     });
   getPermissionAll = () =>
     request(`${SystemConst.API_BASE}/permission/_query/no-paging?paging=false`);
+
+  // 更新全部菜单
+  updateMenus = (data: any) =>
+    request(`${SystemConst.API_BASE}/menu/_all`, { method: 'PATCH', 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' });
+
+  // 更新权限菜单
+  updateRoleMenu = (id: string, data: any) =>
+    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' });
 }
 
 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;

+ 267 - 100
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,21 @@ 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';
 
 export const service = new Service('');
 
@@ -32,26 +38,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 +93,73 @@ 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',
+      render: (record: any) => (
+        <a
+          onClick={() => {
+            Modal.info({
+              title: '当前数据',
+              content: (
+                <div>
+                  <div>寄存器1:{record.number}</div>
+                </div>
+              ),
+              onOk() {},
+            });
+          }}
+        >
+          {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>111111</div>,
+                      onOk() {},
+                    });
+                  }}
+                />
+              )}
+            </>
+          )}
+        </>
+      ),
     },
     {
       title: '状态',
@@ -128,8 +194,8 @@ const NewModbus = () => {
           isPermission={permission.update}
           key="edit"
           onClick={() => {
-            // setVisible(true);
-            // setCurrent(record);
+            setPointDetail(record);
+            setVisiblePoint(true);
           }}
           type={'link'}
           style={{ padding: 0 }}
@@ -154,24 +220,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 +258,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 +279,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, nativeData } = payload;
+        current[pointId] = nativeData;
+        setCurrentData({ ...current });
+        console.log(current);
+      });
+    return () => wsRef.current && wsRef.current?.unsubscribe();
+  }, [pointList]);
 
   return (
     <PageContainer>
@@ -251,7 +355,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 +374,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 +392,8 @@ const NewModbus = () => {
                           isPermission={permission.update}
                           key="edit"
                           onClick={() => {
-                            // setVisible(true);
-                            // setCurrent(record);
+                            setVisible(true);
+                            setCurrent(item);
                           }}
                           type={'link'}
                           style={{ padding: 0 }}
@@ -303,7 +414,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 +435,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,7 +471,7 @@ const NewModbus = () => {
               params={param}
               columns={columns}
               rowKey="id"
-              columnEmptyText={''}
+              // dataSource={dataSoure}
               // scroll={{ x: 1000 }}
               search={false}
               headerTitle={
@@ -375,9 +497,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 +536,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>

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

@@ -5,18 +5,22 @@
     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;
     }
   }
@@ -32,7 +36,7 @@
 .channel-card {
   width: 220px;
   height: 112px;
-  margin-top: 16px;
+  margin-bottom: 16px;
   border: 1px #e0e0e0 solid;
 
   &.active {
@@ -57,6 +61,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;

+ 8 - 6
src/pages/notice/Config/Debug/index.tsx

@@ -214,12 +214,13 @@ const Debug = observer(() => {
     // 应该取选择的配置信息
     if (!state.current) return;
     const templateId = data.templateId;
-    const list = Store.get('notice-template-list');
-    const _template = list.find((item: any) => item.id === templateId);
+    // 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, {
-      template: _template,
-      context: data.variableDefinitions?.reduce(
+    const resp = await service.debug(
+      state?.current.id,
+      templateId,
+      data.variableDefinitions?.reduce(
         (previousValue: any, currentValue: { id: any; value: any }) => {
           return {
             ...previousValue,
@@ -228,9 +229,10 @@ const Debug = observer(() => {
         },
         {},
       ),
-    });
+    );
     if (resp.status === 200) {
       onlyMessage('操作成功!');
+      state.debug = false;
     }
   };
   return (

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

@@ -39,9 +39,9 @@ const Bind = observer((props: Props) => {
         id: 'pages.table.name',
         defaultMessage: '名称',
       }),
-      search: {
-        transform: (value) => ({ name$LIKE: value }),
-      },
+      // search: {
+      //   transform: (value) => ({ name$LIKE: value }),
+      // },
     },
     {
       dataIndex: 'describe',

+ 3 - 3
src/pages/system/Department/Assets/product/index.tsx

@@ -83,9 +83,9 @@ export default observer((props: { parentId: string }) => {
         id: 'pages.table.name',
         defaultMessage: '名称',
       }),
-      search: {
-        transform: (value) => ({ name$LIKE: value }),
-      },
+      // search: {
+      //   transform: (value) => ({ name$LIKE: value }),
+      // },
       width: 180,
       ellipsis: true,
     },

+ 3 - 3
src/pages/system/Department/Member/bind.tsx

@@ -45,9 +45,9 @@ const Bind = observer((props: Props) => {
         id: 'pages.system.username',
         defaultMessage: '用户名',
       }),
-      search: {
-        transform: (value) => ({ username$LIKE: value }),
-      },
+      // search: {
+      //   transform: (value) => ({ username$LIKE: value }),
+      // },
     },
   ];
 

+ 3 - 3
src/pages/system/Department/Member/index.tsx

@@ -61,9 +61,9 @@ const Member = observer((props: { parentId: string }) => {
         id: 'pages.system.username',
         defaultMessage: '用户名',
       }),
-      search: {
-        transform: (value) => ({ username$LIKE: value }),
-      },
+      // search: {
+      //   transform: (value) => ({ username$LIKE: value }),
+      // },
       width: 120,
     },
     {

+ 44 - 0
src/pages/system/Menu/Setting/baseMenu.ts

@@ -618,6 +618,45 @@ export default [
               },
             ],
           },
+          {
+            code: 'device/Firmware',
+            name: '远程升级',
+            parentId: '1-4',
+            id: '1-4-9',
+            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'] },
+            ],
+            buttons: [
+              {
+                id: 'update',
+                name: '编辑',
+                permissions: [{ permission: 'firmware-upgrade-task-manager', actions: ['save'] }],
+              },
+              {
+                id: 'action',
+                name: '启/禁用',
+                permissions: [{ permission: 'firmware-upgrade-task-manager', actions: ['deploy'] }],
+              },
+              {
+                id: 'delete',
+                name: '删除',
+                permissions: [{ permission: 'firmware-upgrade-task-manager', actions: ['delete'] }],
+              },
+              {
+                id: 'add',
+                name: '新增',
+                permissions: [{ permission: 'firmware-upgrade-task-manager', actions: ['save'] }],
+              },
+            ],
+          },
         ],
       },
 
@@ -1301,6 +1340,11 @@ export default [
         buttons: [
           { id: 'view', name: '查看', permissions: [{ permission: 'menu', actions: ['query'] }] },
           {
+            id: 'setting',
+            name: '配置',
+            permissions: [{ permission: 'menu', actions: ['query', 'save', 'grant'] }],
+          },
+          {
             id: 'update',
             name: '编辑',
             permissions: [

+ 1 - 0
src/pages/system/Menu/Setting/index.tsx

@@ -174,6 +174,7 @@ export default observer(() => {
       });
     } else {
       message.warning('请配置系统菜单');
+      setLoading(false);
     }
   };
 

+ 4 - 2
src/pages/system/Platforms/save.tsx

@@ -39,6 +39,8 @@ export default (props: SaveProps) => {
   const [loading, setLoading] = useState(false);
   const { permission: RolePermission } = usePermissions('system/Role');
 
+  console.log(RolePermission);
+
   const SchemaField = createSchemaField({
     components: {
       Checkbox,
@@ -87,7 +89,7 @@ export default (props: SaveProps) => {
           });
         },
       }),
-    [props.data],
+    [props.data, RolePermission],
   );
 
   const getDetail = async (id: string) => {
@@ -116,7 +118,7 @@ export default (props: SaveProps) => {
         form.reset();
       }
     }
-  }, [props.type, props.visible]);
+  }, [props.type, props.visible, RolePermission]);
 
   const schema: ISchema = {
     type: 'object',

+ 21 - 7
src/pages/user/Login/index.tsx

@@ -1,5 +1,5 @@
 import { Button, Checkbox, Divider, message, Spin } from 'antd';
-import React, { useEffect, useRef, useState } from 'react';
+import React, { useEffect, useRef, useState, useMemo } from 'react';
 import { Link } from 'umi';
 import styles from './index.less';
 import Token from '@/utils/token';
@@ -33,14 +33,20 @@ const Login: React.FC = () => {
         ...initialState,
         currentUser: userInfo,
       });
+      return userInfo;
     }
+    return null;
   };
 
   const loginRef = useRef<Partial<LoginParam>>({});
-  const loginForm = createForm({
-    validateFirst: true,
-    initialValues: loginRef.current,
-  });
+  const loginForm = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+        initialValues: loginRef.current,
+      }),
+    [captcha],
+  );
 
   const [loading, setLoading] = useState<boolean>(false);
 
@@ -158,7 +164,15 @@ const Login: React.FC = () => {
       {
         next: async (userInfo) => {
           Token.set(userInfo.token);
-          await fetchUserInfo();
+          const userRef: any = await fetchUserInfo();
+          if (userRef?.user?.username === 'admin') {
+            const initRef = await Service.initPage();
+            if (initRef.status === 200 && !initRef.result.length) {
+              window.location.href = '/#/init-home';
+              setLoading(false);
+              return;
+            }
+          }
           // goto();
           window.location.href = '/';
           setLoading(false);
@@ -174,7 +188,7 @@ const Login: React.FC = () => {
           // setLoading(false);
         },
         complete: () => {
-          getCode();
+          // getCode();
           // setLoading(false);
         },
       },

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

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