wzyyy 3 anos atrás
pai
commit
2466817d95

+ 11 - 0
src/pages/device/Instance/Detail/Opcua/index.less

@@ -0,0 +1,11 @@
+.edit-top {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
+.edit-table {
+  .ant-card-body {
+    padding: 12px 0 0 0 !important;
+  }
+}

+ 458 - 392
src/pages/device/Instance/Detail/Opcua/index.tsx

@@ -1,434 +1,500 @@
-import PermissionButton from '@/components/PermissionButton';
-import { Badge, Card, Empty, Input, Tabs, Tooltip } from 'antd';
-import { useEffect, useMemo, 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/Opcua';
-import Save from '@/pages/link/Channel/Opcua/Save';
-import { InstanceModel } from '@/pages/device/Instance';
-import AddPoint from '@/pages/link/Channel/Opcua/Access/addPoint';
-import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
-import { map } from 'rxjs/operators';
+import { FormItem, ArrayTable, Editable, Select, NumberPicker } from '@formily/antd';
+import { createForm, Field, onFieldReact, FormPath, onFieldChange } from '@formily/core';
+import { FormProvider, createSchemaField } from '@formily/react';
+import { Badge, Card, Empty, Input, Tooltip } from 'antd';
+import { action } from '@formily/reactive';
+import type { Response } from '@/utils/typings';
+import './index.less';
 import { useDomFullHeight } from '@/hooks';
+import PermissionButton from '@/components/PermissionButton';
+import { service } from '@/pages/link/Channel/Modbus';
+import { useEffect, useState } from 'react';
+import { DisconnectOutlined, QuestionCircleOutlined } from '@ant-design/icons';
 import { onlyMessage } from '@/utils/util';
 
-const Opcua = () => {
-  const intl = useIntl();
-  const { permission } = PermissionButton.usePermission('link/Channel/Opcua');
-  const [bindList, setBindList] = useState<any>([]);
-  const [opcId, setOpcId] = useState<string>('');
-  const actionRef = useRef<ActionType>();
-  const [param, setParam] = useState<any>({
-    terms: [{ column: 'opcUaId', value: '' }],
-  });
-  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([]);
+interface Props {
+  data: any;
+}
 
-  const { minHeight } = useDomFullHeight(`.styles.list`);
+export default (props: Props) => {
+  const { data } = props;
+  const { minHeight } = useDomFullHeight('.modbus');
+  const { permission } = PermissionButton.usePermission('link/Channel/Modbus');
+  const [properties, setProperties] = useState<any>([]);
+  const [filterList, setFilterList] = useState<any>([]);
+  const [masterList, setMasterList] = useState<any>([]);
+  const [typeList, setTypeList] = useState<any>([]);
+  const [reload, setReload] = useState<string>('');
+  const [empty, setEmpty] = useState<boolean>(false);
 
-  const columns: ProColumns<any>[] = [
-    {
-      title: '属性ID',
-      dataIndex: 'property',
-      ellipsis: true,
-    },
-    {
-      title: '名称',
-      dataIndex: 'name',
-      ellipsis: true,
-    },
-    {
-      title: 'OPC点位ID',
-      dataIndex: 'opcPointId',
-      ellipsis: true,
-    },
-    {
-      title: '数据类型',
-      dataIndex: 'dataType',
-    },
-    {
-      title: '值',
-      width: 120,
-      render: (record: any) => <>{propertyValue[record?.property] || '-'}</>,
-    },
-    {
-      title: '状态',
-      dataIndex: 'state',
-      width: 90,
-      renderText: (state) => (
-        <Badge text={state?.text} status={state?.value === 'disable' ? '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 !== 'disable' ? 'disable' : 'enable'
-              }.tips`,
-              defaultMessage: '确认禁用?',
-            }),
-            onConfirm: async () => {
-              if (record.state?.value === 'disable') {
-                await service.enablePoint(record.deviceId, [record.id]);
-              } else {
-                await service.stopPoint(record.deviceId, [record.id]);
-              }
-              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 !== 'disable' ? 'disable' : 'enable'}`,
-              defaultMessage: record.state?.value !== 'disable' ? '禁用' : '启用',
-            }),
-          }}
-        >
-          {record.state?.value !== 'disable' ? <StopOutlined /> : <PlayCircleOutlined />}
-        </PermissionButton>,
-        <PermissionButton
-          isPermission={permission.delete}
-          style={{ padding: 0 }}
-          disabled={record.state?.value === 'enable'}
-          tooltip={{
-            title:
-              record.state?.value === 'disable'
-                ? intl.formatMessage({
-                    id: 'pages.data.option.remove',
-                    defaultMessage: '删除',
-                  })
-                : '请先禁用该点位,再删除。',
-          }}
-          popConfirm={{
-            title: '确认删除',
-            disabled: record.state.value === 'enable',
-            onConfirm: async () => {
-              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"
-          type="link"
-        >
-          <DeleteOutlined />
-        </PermissionButton>,
+  //数据类型长度
+  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 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 getOpc = (id: string) => {
-    service
-      .noPagingOpcua({
+  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-opc',
-            value: id,
+            column: 'masterId',
+            value: collectorId,
+          },
+          {
+            column: 'id',
+            value: pointId,
           },
         ],
-      })
-      .then((res: any) => {
-        setBindList(res.result);
-        setFilterList(res.result);
-        setOpcId(res.result?.[0]?.id);
-        setParam({
-          terms: [{ column: 'opcUaId', value: 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 * 2).keys()].map((item: any) => ({
+          label: item,
+          value: item,
+        }));
+        field.loading = false;
+      }),
+    );
   };
 
-  const edit = useMemo(
-    () => (
-      <Save
-        data={channel}
-        close={() => {
-          setVisible(false);
-        }}
-        device={InstanceModel.detail}
-      />
-    ),
-    [channel.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;
+      }),
+    );
+  };
 
-  useEffect(() => {
-    const { id } = InstanceModel.detail;
-    setDeviceId(id);
-    if (id) {
-      getOpc(id);
+  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.property);
-    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.property),
-      history: 1,
-    })
-      ?.pipe(map((res: any) => res.payload))
-      .subscribe((payload: any) => {
-        const { value } = payload;
-        propertyValue[value.property] = value.formatValue;
-        setPropertyValue({ ...propertyValue });
-        // console.log(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);
+        }
+      });
+    service.dataType().then((res) => {
+      if (res.status === 200) {
+        const items = res.result.map((item: any) => ({
+          label: item.name,
+          value: item.id,
+        }));
+        setTypeList(items);
+      }
+    });
+  }, []);
+  useEffect(() => {
+    console.log(typeList);
+    const metadata = JSON.parse(data.metadata).properties?.map((item: any) => ({
+      metadataId: item.id,
+      metadataName: `${item.name}(${item.id})`,
+      metadataType: 'property',
+    }));
+    if (metadata && metadata.length !== 0) {
+      service.getDevicePoint(data.id).then((res) => {
+        if (res.status === 200) {
+          // console.log(res.result)
+          const array = res.result.reduce((x: any, y: any) => {
+            const metadataId = metadata.find((item: any) => item.metadataId === y.metadataId);
+            if (metadataId) {
+              Object.assign(metadataId, y);
+            } else {
+              x.push(y);
+            }
+            return x;
+          }, metadata);
+          //删除物模型
+          const items = array.filter((item: any) => item.metadataName);
+          setProperties(items);
+          setFilterList(items);
+          const delList = array.filter((a: any) => !a.metadataName).map((b: any) => b.id);
+          //删除后解绑
+          if (delList && delList.length !== 0) {
+            service.removeDevicePoint(data.id, delList);
+          }
+        }
+      });
+    } else {
+      setEmpty(true);
+    }
+  }, [reload]);
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Editable,
+      ArrayTable,
+      Select,
+      NumberPicker,
+      Render,
+      ActionButton,
+      StatusRender,
+    },
+  });
+
+  const form = createForm({
+    values: {
+      array: filterList,
+    },
+    effects: () => {
+      onFieldReact('array.*.collectorId', async (field, f) => {
+        const value = (field as Field).value;
+        const path = FormPath.transform(
+          field.path,
+          /\d+/,
+          (index) => `array.${parseInt(index)}.pointId`,
+        );
+        const path1 = FormPath.transform(
+          field.path,
+          /\d+/,
+          (index) => `array.${parseInt(index)}.codec`,
+        );
+        f.setFieldState(path, (state) => {
+          if (value) {
+            state.required = true;
+            form.validate();
+          } else {
+            state.required = false;
+            form.validate();
+          }
+        });
+        f.setFieldState(path1, (state) => {
+          if (value) {
+            state.required = true;
+            form.validate();
+          } else {
+            state.required = false;
+            form.validate();
+          }
+        });
+      });
+      onFieldChange('array.*.codec', (field: any) => {
+        const value = (field as Field).value;
+        const path = FormPath.transform(
+          field.path,
+          /\d+/,
+          (index) => `array.${parseInt(index)}.codecConfiguration.readIndex`,
+        );
+        if ((field as Field).modified) {
+          const readIndex = field.query(path).get('value');
+          const dataLength = field.query(path).get('dataSource')?.length - 1;
+          const length = lengthMap.get(value) + readIndex;
+          console.log(length, dataLength);
+          if (length > dataLength) {
+            field.selfErrors = '数据类型对应的长度和起始位置加起来不能超过数据长度';
+          } else {
+            field.selfErrors = '';
+          }
+        }
       });
-  }, [data]);
+    },
+  });
+
+  const schema = {
+    type: 'object',
+    properties: {
+      array: {
+        type: 'array',
+        'x-decorator': 'FormItem',
+        'x-component': 'ArrayTable',
+        'x-component-props': {
+          pagination: {
+            pageSize: 10,
+          },
+          scroll: { x: '100%' },
+        },
+        items: {
+          type: 'object',
+          properties: {
+            column1: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { width: 120, title: '属性' },
+              properties: {
+                metadataId: {
+                  type: 'string',
+                  'x-component': 'Render',
+                },
+              },
+            },
+            column2: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { width: 200, title: '通道' },
+              properties: {
+                collectorId: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Select',
+                  'x-component-props': {
+                    placeholder: '请选择',
+                    showSearch: true,
+                    allowClear: true,
+                    showArrow: true,
+                    filterOption: (input: string, option: any) =>
+                      option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                  },
+                  enum: masterList,
+                },
+              },
+            },
+            column3: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 200,
+                title: (
+                  <>
+                    点位名称
+                    <Tooltip title="名称(从站ID/地址/功能码)">
+                      <QuestionCircleOutlined />
+                    </Tooltip>
+                  </>
+                ),
+              },
+              properties: {
+                pointId: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Select',
+                  'x-component-props': {
+                    placeholder: '请选择',
+                    showSearch: true,
+                    allowClear: true,
+                    showArrow: true,
+                    filterOption: (input: string, option: any) =>
+                      option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                  },
+                  'x-reactions': ['{{useAsyncDataSource(getName)}}'],
+                },
+              },
+            },
+            column6: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 100,
+                title: '状态',
+                sorter: (a: any, b: any) => a.state.value.length - b.state.value.length,
+              },
+              properties: {
+                id: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'StatusRender',
+                },
+              },
+            },
+            column7: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: '操作',
+                dataIndex: 'operations',
+                width: 50,
+                fixed: 'right',
+              },
+              properties: {
+                item: {
+                  type: 'void',
+                  'x-component': 'FormItem',
+                  properties: {
+                    remove: {
+                      type: 'void',
+                      'x-component': 'ActionButton',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  };
 
   return (
-    <Card className={styles.list} style={{ minHeight }}>
-      <div style={{ display: 'flex' }}>
-        <div>
-          <div style={{ width: '250px', marginTop: 15 }}>
+    <Card className="modbus" style={{ minHeight }}>
+      {empty ? (
+        <Empty description={'暂无数据,请配置物模型'} style={{ marginTop: '10%' }} />
+      ) : (
+        <>
+          <div className="edit-top">
             <Input.Search
-              placeholder="请输入通道名称"
+              placeholder="请输入属性ID"
               allowClear
+              style={{ width: 190 }}
               onSearch={(value) => {
                 if (value) {
-                  const items = bindList.filter((item: any) => item.name.match(value));
+                  const items = properties.filter((item: any) => item.metadataId.match(value));
                   setFilterList(items);
-                  if (items.length === 0) {
-                    setParam({
-                      terms: [{ column: 'opcUaId', value: '' }],
-                    });
-                  } else {
-                    setParam({
-                      terms: [{ column: 'opcUaId', value: items[0]?.id }],
-                    });
-                    setOpcId(items[0]?.id);
-                  }
                 } else {
-                  setFilterList(bindList);
-                  if (opcId) {
-                    setParam({
-                      terms: [{ column: 'opcUaId', value: opcId }],
-                    });
-                  } else {
-                    setParam({
-                      terms: [{ column: 'opcUaId', value: '' }],
-                    });
-                  }
+                  setFilterList(properties);
                 }
               }}
             />
             <PermissionButton
-              onClick={() => {
-                setVisible(true);
-                setChannel({});
+              onClick={async () => {
+                const value: any = await form.submit();
+                const items = value.array.filter((item: any) => item.collectorId);
+                save(items);
               }}
               isPermission={permission.add}
               key="add"
-              icon={<PlusOutlined />}
-              type="dashed"
-              style={{ width: '100%', margin: '16px 0 18px 0' }}
+              type="primary"
+              style={{ marginRight: 10 }}
             >
-              新增通道
+              保存
             </PermissionButton>
           </div>
-          {filterList.length > 0 ? (
-            <Tabs
-              style={{ height: 600 }}
-              tabPosition={'left'}
-              activeKey={opcId}
-              onChange={(e) => {
-                setOpcId(e);
-                setParam({
-                  terms: [{ column: 'opcUaId', value: 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>
-                      <div>
-                        <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) {
-                                getOpc(deviceId);
-                                onlyMessage(
-                                  intl.formatMessage({
-                                    id: 'pages.data.option.success',
-                                    defaultMessage: '操作成功!',
-                                  }),
-                                );
-                              }
-                            },
-                          }}
-                          key="delete"
-                          type="link"
-                        >
-                          <DeleteOutlined />
-                        </PermissionButton>
-                      </div>
-                    </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 (Object.keys(params).length && !params.terms[0].value) {
-                return {
-                  code: 200,
-                  result: {
-                    data: [],
-                    pageIndex: 0,
-                    pageSize: 0,
-                    total: 0,
-                  },
-                  status: 200,
-                };
-              }
-              const res = await service.PointList({
-                ...params,
-                sorts: [{ name: 'createTime', order: 'desc' }],
-              });
-              setData(res.result.data);
-              return {
-                code: res.message,
-                result: {
-                  data: res.result.data,
-                  pageIndex: res.result.pageIndex,
-                  pageSize: res.result.pageSize,
-                  total: res.result.total,
-                },
-                status: res.status,
-              };
-            }}
-          />
-        </div>
-      </div>
-
-      {visible && edit}
-      {pointVisiable && (
-        <AddPoint
-          deviceId={deviceId}
-          opcUaId={opcId}
-          data={current}
-          close={() => {
-            setPointVisiable(false);
-            actionRef.current?.reload();
-          }}
-        />
+          <div className="edit-table">
+            <FormProvider form={form}>
+              <SchemaField
+                schema={schema}
+                scope={{ useAsyncDataSource, getName, getMaster, getQuantity, useAsync }}
+              />
+            </FormProvider>
+          </div>
+        </>
       )}
     </Card>
   );
 };
-export default Opcua;

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

@@ -199,7 +199,7 @@ const InstanceDetail = observer(() => {
         datalist.push({
           key: 'opcua',
           tab: 'OPC UA',
-          component: <Opcua />,
+          component: <Opcua data={InstanceModel.detail} />,
         });
       }
       if (response.result.deviceType?.value === 'gateway') {

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

@@ -597,7 +597,6 @@ const NewModbus = () => {
             setImportVisible(false);
             actionRef.current?.reload();
           }}
-          // visible={importVisible}
         />
       )}
       {exportVisible && masterMemo}

+ 0 - 258
src/pages/link/Channel/Opcua/Access/addPoint/index.tsx

@@ -1,258 +0,0 @@
-import { Col, Form, Input, InputNumber, Modal, Radio, Row, Select } from 'antd';
-import { useEffect, useState } from 'react';
-import { service } from '@/pages/link/Channel/Opcua';
-import { DataTypeList } from '@/pages/device/data';
-import { onlyMessage } from '@/utils/util';
-
-interface Props {
-  data: any;
-  deviceId: string;
-  opcUaId: string;
-  close: Function;
-}
-
-const AddPoint = (props: Props) => {
-  const [form] = Form.useForm();
-  const [enableCalculate, setEnableCalculate] = useState<boolean>();
-  const [property, setProperty] = useState<any>([]);
-
-  const handleSave = async () => {
-    const formData = await form.validateFields();
-    const { name } = property?.find((item: any) => item.id === formData.property);
-    if (props.data.id) {
-      if (formData.enableCalculate) {
-        service
-          .editPoint(
-            {
-              ...formData,
-              name: name,
-              deviceId: props.deviceId,
-              opcUaId: props.opcUaId,
-              calculateType: 'number',
-              configuration: {
-                initialValue: formData.initialValue,
-                multiple: formData.multiple,
-              },
-            },
-            props.data.id,
-          )
-          .then((res) => {
-            if (res.status === 200) {
-              onlyMessage('保存成功');
-              props.close();
-            }
-          });
-      } else {
-        service
-          .editPoint(
-            {
-              ...formData,
-              name: name,
-              deviceId: props.deviceId,
-              opcUaId: props.opcUaId,
-            },
-            props.data.id,
-          )
-          .then((res) => {
-            if (res.status === 200) {
-              onlyMessage('保存成功');
-              props.close();
-            }
-          });
-      }
-    } else {
-      if (formData.enableCalculate) {
-        service
-          .addPoint({
-            ...formData,
-            name: name,
-            deviceId: props.deviceId,
-            opcUaId: props.opcUaId,
-            calculateType: 'number',
-            configuration: {
-              initialValue: formData.initialValue,
-              multiple: formData.multiple,
-            },
-          })
-          .then((res) => {
-            if (res.status === 200) {
-              onlyMessage('保存成功');
-              props.close();
-            }
-          });
-      } else {
-        service
-          .addPoint({
-            ...formData,
-            name: name,
-            deviceId: props.deviceId,
-            opcUaId: props.opcUaId,
-          })
-          .then((res) => {
-            if (res.status === 200) {
-              onlyMessage('保存成功');
-              props.close();
-            }
-          });
-      }
-    }
-  };
-
-  useEffect(() => {
-    console.log(props.data);
-    service.deviceDetail(props.deviceId).then((res) => {
-      if (res.result.metadata) {
-        const item = JSON.parse(res.result?.metadata);
-        setProperty(item.properties);
-      }
-    });
-    if (props.data.enableCalculate) {
-      setEnableCalculate(true);
-    }
-  }, []);
-  return (
-    <Modal
-      title={props.data.id ? '编辑' : '新增'}
-      visible
-      width="40vw"
-      destroyOnClose
-      onOk={handleSave}
-      onCancel={() => {
-        props.close();
-      }}
-    >
-      <Form
-        form={form}
-        layout="vertical"
-        initialValues={{
-          ...props.data,
-          enableCalculate: props.data.enableCalculate || false,
-          initialValue: props.data?.configuration?.initialValue,
-          multiple: props.data?.configuration?.multiple,
-        }}
-      >
-        <Row gutter={[24, 24]}>
-          <Col span={24}>
-            <Form.Item
-              label="OPC点位ID"
-              required
-              name="opcPointId"
-              rules={[
-                { type: 'string', max: 64 },
-                { required: true, message: '请输入OPC点位ID' },
-              ]}
-            >
-              <Input placeholder="请输入OPC点位ID" />
-            </Form.Item>
-          </Col>
-        </Row>
-        <Row gutter={[24, 24]}>
-          <Col span={12}>
-            <Form.Item
-              label="属性ID"
-              name="property"
-              required
-              rules={[{ required: true, message: '属性必选' }]}
-            >
-              <Select placeholder="请选择属性">
-                {property.map((item: any) => (
-                  <Select.Option value={item.id} key={item.id}>
-                    {item.name}
-                  </Select.Option>
-                ))}
-              </Select>
-            </Form.Item>
-          </Col>
-          <Col span={12}>
-            <Form.Item
-              label="数据类型"
-              name="dataType"
-              required
-              rules={[{ required: true, message: '数据类型必选' }]}
-            >
-              <Select placeholder="请选择数据类型">
-                {DataTypeList.map((item) => (
-                  <Select.Option value={item.value} key={item.value}>
-                    {item.label}
-                  </Select.Option>
-                ))}
-              </Select>
-            </Form.Item>
-          </Col>
-        </Row>
-        <Row gutter={[24, 24]}>
-          <Col span={12}>
-            <Form.Item
-              label="数据模式"
-              name="dataMode"
-              required
-              rules={[{ required: true, message: '数据模式必选' }]}
-            >
-              <Select placeholder="请选择数据模式">
-                <Select.Option value="pull" key={'pull'}>
-                  拉取
-                </Select.Option>
-                <Select.Option value="sub" key={'sub'}>
-                  订阅
-                </Select.Option>
-              </Select>
-            </Form.Item>
-          </Col>
-          <Col span={12}>
-            <Form.Item
-              label="采样频率"
-              name="interval"
-              required
-              rules={[{ required: true, message: '采样频率必填' }]}
-            >
-              <InputNumber style={{ width: '100%' }} placeholder="请输入采样频率" min={1} />
-            </Form.Item>
-          </Col>
-        </Row>
-        <Row gutter={[24, 24]}>
-          <Col span={24}>
-            <Form.Item
-              label="开启计算"
-              name="enableCalculate"
-              required
-              rules={[{ required: true, message: '开启计算必选' }]}
-            >
-              <Radio.Group
-                buttonStyle="solid"
-                onChange={(e) => {
-                  console.log(e.target.value);
-                  setEnableCalculate(e.target.value);
-                }}
-              >
-                <Radio.Button value={true}>是</Radio.Button>
-                <Radio.Button value={false}>否</Radio.Button>
-              </Radio.Group>
-            </Form.Item>
-          </Col>
-        </Row>
-        {enableCalculate && (
-          <Row gutter={[24, 24]}>
-            <Col span={12}>
-              <Form.Item label="初始值" name="initialValue" required>
-                <Input placeholder="请输入初始值" />
-              </Form.Item>
-            </Col>
-            <Col span={12}>
-              <Form.Item label="倍数" name="multiple" required>
-                <InputNumber style={{ width: '100%' }} min={1} placeholder="请输入倍数" />
-              </Form.Item>
-            </Col>
-          </Row>
-        )}
-        <Row gutter={[24, 24]}>
-          <Col span={24}>
-            <Form.Item label="说明" name="description">
-              <Input.TextArea maxLength={200} placeholder="请输入说明" />
-            </Form.Item>
-          </Col>
-        </Row>
-      </Form>
-    </Modal>
-  );
-};
-export default AddPoint;

+ 0 - 150
src/pages/link/Channel/Opcua/Access/bindDevice/index.tsx

@@ -1,150 +0,0 @@
-import { Modal } from '@/components';
-import SearchComponent from '@/components/SearchComponent';
-import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Badge } from 'antd';
-import { useRef, useState } from 'react';
-import { service } from '@/pages/link/Channel/Opcua';
-import moment from 'moment';
-import { onlyMessage } from '@/utils/util';
-
-interface Props {
-  id: string;
-  close: () => void;
-}
-
-const BindDevice = (props: Props) => {
-  const actionRef = useRef<ActionType>();
-  const [param, setParam] = useState({});
-  const [keys, setKeys] = useState<any>([]);
-  const [bindDevice, setBindDevice] = useState<any>([]);
-
-  const statusMap = new Map();
-  statusMap.set('在线', 'success');
-  statusMap.set('离线', 'error');
-  statusMap.set('禁用', 'processing');
-  statusMap.set('online', 'success');
-  statusMap.set('offline', 'error');
-  statusMap.set('notActive', 'processing');
-
-  const columns: ProColumns<any>[] = [
-    {
-      title: '设备ID',
-      dataIndex: 'id',
-      ellipsis: true,
-    },
-    {
-      title: '设备名称',
-      dataIndex: 'name',
-      width: 200,
-      ellipsis: true,
-    },
-    {
-      title: '产品名称',
-      dataIndex: 'productName',
-      ellipsis: true,
-    },
-    {
-      title: '注册时间',
-      // dataIndex: 'registryTime',
-      render: (_, record) => (
-        <>{record.registryTime ? moment(record.registryTime).format('YYYY-MM-DD HH:mm:ss') : '-'}</>
-      ),
-    },
-    {
-      title: '状态',
-      dataIndex: 'state',
-      renderText: (state) => <Badge text={state?.text} status={statusMap.get(state.value)} />,
-      valueType: 'select',
-      valueEnum: {
-        online: {
-          text: '在线',
-          status: 'disabled',
-        },
-        offline: {
-          text: '离线',
-          status: 'offline',
-        },
-        notActive: {
-          text: '禁用',
-          status: 'notActive',
-        },
-      },
-    },
-  ];
-
-  const save = () => {
-    if (bindDevice && bindDevice.length !== 0) {
-      const params = bindDevice.map((item: any) => ({
-        opcUaId: props.id,
-        deviceId: item.id,
-        deviceName: item.name,
-        productId: item.productId,
-        productName: item.productName,
-      }));
-      service.bind(params).then((res) => {
-        if (res.status === 200) {
-          onlyMessage('绑定成功');
-          props.close();
-        }
-      });
-    } else {
-      onlyMessage('请勾选数据', 'error');
-    }
-  };
-
-  // useEffect(() => {
-  //   console.log(props.id);
-  // }, []);
-
-  return (
-    <Modal
-      title={'绑定设备'}
-      maskClosable={false}
-      visible
-      onCancel={props.close}
-      onOk={() => {
-        save();
-      }}
-      width={1300}
-      permissionCode={'device/Instance'}
-      permission={['edit', 'view']}
-    >
-      <SearchComponent
-        field={columns}
-        model={'simple'}
-        target="bindDevice"
-        defaultParam={[
-          { column: 'productId$dev-protocol', value: 'opc-ua' },
-          { column: 'id$opc-bind$not', value: props.id, type: 'and' },
-        ]}
-        onSearch={(data) => {
-          // 重置分页数据
-          actionRef.current?.reset?.();
-          setParam(data);
-        }}
-      />
-      <ProTable
-        actionRef={actionRef}
-        params={param}
-        columns={columns}
-        rowKey="id"
-        search={false}
-        columnEmptyText={''}
-        request={async (params) =>
-          service.getDevice({
-            ...params,
-            sorts: [{ name: 'createTime', order: 'desc' }],
-          })
-        }
-        rowSelection={{
-          selectedRowKeys: keys,
-          onChange: (selectedRowKeys, selectedRows) => {
-            setBindDevice(selectedRows);
-            setKeys(selectedRowKeys);
-          },
-        }}
-      />
-    </Modal>
-  );
-};
-export default BindDevice;

+ 0 - 44
src/pages/link/Channel/Opcua/Access/index.less

@@ -1,44 +0,0 @@
-.list {
-  :global {
-    .ant-tabs-tab.ant-tabs-tab-active {
-      background-color: #fafafa;
-    }
-    .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
-      color: #1d39c4;
-      text-shadow: none;
-    }
-
-    .ant-tabs-content-holder {
-      width: 1px;
-    }
-
-    .ant-tabs > .ant-tabs-nav .ant-tabs-nav-more,
-    .ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-more {
-      display: none;
-    }
-  }
-}
-
-.left {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  width: 200px;
-  .text {
-    width: 130px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-align: left;
-    text-overflow: ellipsis;
-    word-break: break-all;
-  }
-  .icon {
-    display: none;
-  }
-}
-
-.left:hover {
-  .icon {
-    display: block;
-  }
-}

+ 0 - 447
src/pages/link/Channel/Opcua/Access/index.tsx

@@ -1,447 +0,0 @@
-import PermissionButton from '@/components/PermissionButton';
-import { PageContainer } from '@ant-design/pro-layout';
-import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Badge, Card, Empty, Input, Popconfirm, Tabs, Tooltip } from 'antd';
-import { useIntl, useLocation } from 'umi';
-import { useEffect, useRef, useState } from 'react';
-import {
-  DeleteOutlined,
-  DisconnectOutlined,
-  EditOutlined,
-  PlayCircleOutlined,
-  PlusOutlined,
-  StopOutlined,
-} from '@ant-design/icons';
-import BindDevice from '@/pages/link/Channel/Opcua/Access/bindDevice';
-import { service } from '@/pages/link/Channel/Opcua';
-import encodeQuery from '@/utils/encodeQuery';
-import styles from './index.less';
-import AddPoint from './addPoint';
-import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
-import { map } from 'rxjs/operators';
-import { useDomFullHeight } from '@/hooks';
-import { onlyMessage } from '@/utils/util';
-
-const Access = () => {
-  const intl = useIntl();
-  const actionRef = useRef<ActionType>();
-  const location = useLocation<string>();
-  const [param, setParam] = useState<any>({
-    terms: [{ column: 'deviceId', value: '' }],
-  });
-  const [opcUaId, setOpcUaId] = useState<any>('');
-  const { permission } = PermissionButton.usePermission('link/Channel/Opcua');
-  const [deviceVisiable, setDeviceVisiable] = useState<boolean>(false);
-  const [pointVisiable, setPointVisiable] = useState<boolean>(false);
-  const [bindList, setBindList] = useState<any>([]);
-  const [deviceId, setDeviceId] = useState<string>('');
-  const [productId, setProductId] = useState<string>('');
-  const [current, setCurrent] = useState<any>({});
-  const [data, setData] = useState<any>([]);
-  const [subscribeTopic] = useSendWebsocketMessage();
-  const [propertyValue, setPropertyValue] = useState<any>({});
-  const [bindDeviceId, setBindDeviceId] = useState<any>('');
-  const wsRef = useRef<any>();
-  const [filterList, setFilterList] = useState([]);
-  const { minHeight } = useDomFullHeight(`.opcuaAccess`, 26);
-
-  const columns: ProColumns<any>[] = [
-    {
-      title: '属性ID',
-      dataIndex: 'property',
-      ellipsis: true,
-    },
-    {
-      title: '名称',
-      dataIndex: 'name',
-      ellipsis: true,
-    },
-    {
-      title: 'OPC点位ID',
-      dataIndex: 'opcPointId',
-      ellipsis: true,
-    },
-    {
-      title: '数据类型',
-      dataIndex: 'dataType',
-    },
-    {
-      title: '值',
-      width: 100,
-      render: (record: any) => <>{propertyValue[record?.property] || '-'}</>,
-    },
-    {
-      title: '状态',
-      dataIndex: 'state',
-      width: 100,
-      renderText: (state) => (
-        <Badge text={state?.text} status={state?.value === 'disable' ? '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 !== 'disable' ? 'disable' : 'enable'
-              }.tips`,
-              defaultMessage: '确认禁用?',
-            }),
-            onConfirm: async () => {
-              console.log(record);
-              if (record.state.value === 'disable') {
-                await service.enablePoint(bindDeviceId, [record.id]);
-              } else {
-                await service.stopPoint(bindDeviceId, [record.id]);
-              }
-              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 !== 'disable' ? 'disable' : 'enable'}`,
-              defaultMessage: record.state.value !== 'disable' ? '禁用' : '启用',
-            }),
-          }}
-        >
-          {record.state.value !== 'disable' ? <StopOutlined /> : <PlayCircleOutlined />}
-        </PermissionButton>,
-        <PermissionButton
-          isPermission={permission.delete}
-          style={{ padding: 0 }}
-          disabled={record.state.value === 'enable'}
-          tooltip={{
-            title:
-              record.state.value === 'disable'
-                ? intl.formatMessage({
-                    id: 'pages.data.option.remove',
-                    defaultMessage: '删除',
-                  })
-                : '请先禁用该点位,再删除。',
-          }}
-          popConfirm={{
-            title: '确认删除',
-            disabled: record.state.value === 'enable',
-            onConfirm: async () => {
-              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"
-          type="link"
-        >
-          <DeleteOutlined />
-        </PermissionButton>,
-      ],
-    },
-  ];
-
-  const getBindList = (params: any) => {
-    service.getBindList(params).then((res: any) => {
-      if (res.status === 200) {
-        if (res.result && res.result !== 0) {
-          setProductId(res.result[0]?.productId);
-          setBindDeviceId(res.result[0]?.id);
-          setBindList(res.result);
-          setFilterList(res.result);
-          setDeviceId(res.result[0]?.deviceId);
-          setParam({
-            terms: [{ column: 'deviceId', value: res.result[0]?.deviceId }],
-          });
-        }
-      }
-    });
-  };
-
-  useEffect(() => {
-    if (productId && deviceId) {
-      const point = data.map((item: any) => item.property);
-      const id = `instance-info-property-${deviceId}-${productId}-${point.join('-')}`;
-      const topic = `/dashboard/device/${productId}/properties/realTime`;
-      wsRef.current = subscribeTopic?.(id, topic, {
-        deviceId: deviceId,
-        properties: data.map((item: any) => item.property),
-        history: 1,
-      })
-        ?.pipe(map((res) => res.payload))
-        .subscribe((payload: any) => {
-          const { value } = payload;
-          propertyValue[value.property] = value.formatValue;
-          setPropertyValue({ ...propertyValue });
-          // console.log(propertyValue)
-        });
-    }
-    return () => wsRef.current && wsRef.current?.unsubscribe();
-  }, [data]);
-
-  useEffect(() => {
-    const item = new URLSearchParams(location.search);
-    const id = item.get('id');
-    if (id) {
-      setOpcUaId(id);
-      getBindList(
-        encodeQuery({
-          terms: {
-            opcUaId: id,
-          },
-        }),
-      );
-    }
-  }, []);
-
-  return (
-    <PageContainer>
-      <Card className={styles.list}>
-        <div className="opcuaAccess" style={{ display: 'flex', minHeight }}>
-          <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({
-                        terms: [{ column: 'deviceId', value: '' }],
-                      });
-                    } else {
-                      setParam({
-                        terms: [{ column: 'deviceId', value: items[0]?.deviceId }],
-                      });
-                      setDeviceId(items[0]?.deviceId);
-                    }
-                  } else {
-                    setFilterList(bindList);
-                    if (deviceId) {
-                      setParam({
-                        terms: [{ column: 'deviceId', value: deviceId }],
-                      });
-                    } else {
-                      setParam({
-                        terms: [{ column: 'deviceId', value: '' }],
-                      });
-                    }
-                  }
-                }}
-              />
-              <PermissionButton
-                onClick={() => {
-                  setDeviceVisiable(true);
-                }}
-                isPermission={permission.add}
-                key="add"
-                icon={<PlusOutlined />}
-                type="dashed"
-                style={{ width: '100%', margin: '16px 0 18px 0' }}
-              >
-                绑定设备
-              </PermissionButton>
-            </div>
-            {filterList.length > 0 ? (
-              <Tabs
-                tabPosition={'left'}
-                // defaultActiveKey={deviceId}
-                activeKey={deviceId}
-                style={{ height: 600 }}
-                onChange={(e) => {
-                  setDeviceId(e);
-                  const items = bindList.find((item: any) => item.deviceId === e);
-                  setProductId(items?.productId);
-                  setBindDeviceId(items?.id);
-                  setParam({
-                    terms: [{ column: 'deviceId', value: e }],
-                  });
-                }}
-              >
-                {filterList.map((item: any) => (
-                  <Tabs.TabPane
-                    key={item.deviceId}
-                    tab={
-                      <div className={styles.left}>
-                        <Tooltip title={item.name}>
-                          <div className={styles.text}>{item.name}</div>
-                        </Tooltip>
-                        <Popconfirm
-                          title="确认解绑该设备嘛?"
-                          onConfirm={() => {
-                            service.unbind([item.deviceId], opcUaId).then((res: any) => {
-                              if (res.status === 200) {
-                                onlyMessage('解绑成功');
-                                getBindList(
-                                  encodeQuery({
-                                    terms: {
-                                      opcUaId: opcUaId,
-                                    },
-                                  }),
-                                );
-                              }
-                            });
-                          }}
-                          okText="是"
-                          cancelText="否"
-                        >
-                          <DisconnectOutlined className={styles.icon} />
-                        </Popconfirm>
-                      </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>
-                  <div style={{ marginLeft: 10 }}>
-                    <Input.Search
-                      placeholder="请输入属性"
-                      allowClear
-                      onSearch={(value) => {
-                        console.log(value);
-                        if (value) {
-                          setParam({
-                            terms: [
-                              { column: 'deviceId', value: deviceId },
-                              { column: 'property', value: `%${value}%`, termType: 'like' },
-                            ],
-                          });
-                        } else {
-                          setParam({
-                            terms: [{ column: 'deviceId', value: deviceId }],
-                          });
-                        }
-                      }}
-                    />
-                  </div>
-                </>
-              }
-              request={async (params) => {
-                if (Object.keys(params).length && !params.terms[0].value) {
-                  return {
-                    code: 200,
-                    result: {
-                      data: [],
-                      pageIndex: 0,
-                      pageSize: 0,
-                      total: 0,
-                    },
-                    status: 200,
-                  };
-                }
-                const res = await service.PointList({
-                  ...params,
-                  sorts: [{ name: 'createTime', order: 'desc' }],
-                });
-                setData(res.result.data);
-                return {
-                  code: res.message,
-                  result: {
-                    data: res.result.data,
-                    pageIndex: res.result.pageIndex,
-                    pageSize: res.result.pageSize,
-                    total: res.result.total,
-                  },
-                  status: res.status,
-                };
-              }}
-            />
-          </div>
-        </div>
-      </Card>
-      {deviceVisiable && (
-        <BindDevice
-          id={opcUaId}
-          close={() => {
-            setDeviceVisiable(false);
-            getBindList(
-              encodeQuery({
-                terms: {
-                  opcUaId: opcUaId,
-                },
-              }),
-            );
-          }}
-        />
-      )}
-      {pointVisiable && (
-        <AddPoint
-          deviceId={deviceId}
-          opcUaId={opcUaId}
-          data={current}
-          close={() => {
-            setPointVisiable(false);
-            actionRef.current?.reload();
-          }}
-        />
-      )}
-    </PageContainer>
-  );
-};
-export default Access;

+ 124 - 0
src/pages/link/Channel/Opcua/Export/index.tsx

@@ -0,0 +1,124 @@
+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/Opcua';
+import SystemConst from '@/utils/const';
+import { downloadFile } from '@/utils/util';
+
+interface Props {
+  close: () => void;
+  data: any;
+}
+
+const Export = (props: Props) => {
+  const { close } = props;
+  const [list, setList] = useState<any[]>([]);
+  const SchemaField = createSchemaField({
+    components: {
+      Radio,
+      Select,
+      FormItem,
+      FormLayout,
+    },
+  });
+
+  useEffect(() => {
+    service.noPagingOpcua({ 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: {
+          opcUaId: {
+            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;
+    const opcUaName = props.data.find((item: any) => item.id === values.opcUaId)?.name;
+    if (values) {
+      downloadFile(
+        `/${SystemConst.API_BASE}/opc/point/${values.opcUaId}/export.${values.fileType}`,
+        {
+          opcUaName: opcUaName,
+        },
+      );
+      close();
+    }
+  };
+  return (
+    <Modal
+      maskClosable={false}
+      visible
+      onCancel={() => close()}
+      width="35vw"
+      title="导出"
+      onOk={downloadTemplate}
+    >
+      <div style={{ marginTop: '20px' }}>
+        <FormProvider form={form}>
+          <SchemaField schema={schema} />
+        </FormProvider>
+      </div>
+    </Modal>
+  );
+};
+export default Export;

+ 0 - 298
src/pages/link/Channel/Opcua/Save/index.tsx

@@ -1,298 +0,0 @@
-import { useIntl } from 'umi';
-import type { Field } from '@formily/core';
-import { createForm } from '@formily/core';
-import { createSchemaField } from '@formily/react';
-import { Form, FormGrid, FormItem, Input, Select } from '@formily/antd';
-import type { ISchema } from '@formily/json-schema';
-import { service } from '@/pages/link/Channel/Opcua';
-import { Modal } from '@/components';
-import { useMemo } from 'react';
-import { action } from '@formily/reactive';
-import type { Response } from '@/utils/typings';
-import { onlyMessage } from '@/utils/util';
-
-interface Props {
-  data: Partial<OpaUa>;
-  close: () => void;
-  device?: any;
-}
-
-const Save = (props: Props) => {
-  const intl = useIntl();
-  // const [policies, setPolicies] = useState<any>([]);
-  // const [modes, setModes] = useState<any>([]);
-
-  const form = useMemo(
-    () =>
-      createForm({
-        validateFirst: true,
-        initialValues: {
-          ...props.data,
-          clientConfigs: props.data?.clientConfigs?.[0],
-        },
-      }),
-    [props.data.id],
-  );
-
-  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,
-    },
-  });
-
-  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: '请输入名称',
-              },
-            ],
-          },
-          'clientConfigs.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,
-          },
-          'clientConfigs.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)}}'],
-          },
-          'clientConfigs.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)}}'],
-          },
-          'clientConfigs.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个字符',
-              },
-            ],
-          },
-          'clientConfigs.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>();
-    console.log(value);
-    const item = {
-      name: value.name,
-      description: value.description,
-      clientConfigs: [value.clientConfigs],
-    };
-    if (props.data.id) {
-      service.modify(props.data.id, item).then((res: any) => {
-        if (res.status === 200) {
-          onlyMessage('保存成功');
-          props.close();
-        }
-      });
-    } else {
-      if (props.device) {
-        service.save(item).then((res: any) => {
-          if (res.status === 200) {
-            const params = {
-              opcUaId: res.result.id,
-              deviceId: props.device.id,
-              deviceName: props.device.name,
-              productId: props.device.productId,
-              productName: props.device.productName,
-            };
-            service.bind(params).then((resp) => {
-              if (resp.status === 200) {
-                onlyMessage('保存成功');
-                props.close();
-              }
-            });
-          }
-        });
-      } else {
-        service.save(item).then((res: any) => {
-          if (res.status === 200) {
-            onlyMessage('保存成功');
-            props.close();
-          }
-        });
-      }
-    }
-  };
-
-  // useEffect(() => {
-  //   service.policies().then((res) => setPolicies(res.result));
-  //   service.modes().then((res) => setModes(res.result));
-  // }, []);
-  return (
-    <Modal
-      title={intl.formatMessage({
-        id: `pages.data.option.${props.data.id ? 'edit' : 'add'}`,
-        defaultMessage: '编辑',
-      })}
-      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 Save;

+ 244 - 0
src/pages/link/Channel/Opcua/import/index.tsx

@@ -0,0 +1,244 @@
+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 {
+  close: () => void;
+  opcId: any;
+}
+const FileFormat = (props: any) => {
+  const [data, setData] = useState<{ fileType: 'xlsx' | 'csv' }>({
+    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 [fileName, setFileName] = useState<any>('');
+
+  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}/opc/point/${
+          props.opcId
+        }/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') {
+              console.log(info.file);
+              const name = info.file.name.split('.')?.[0];
+              setFileName(name);
+              console.log(name);
+              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}/opc/point/template.xlsx`;
+              downloadFile(url);
+            }}
+          >
+            .xlsx
+          </a>
+          <a
+            style={{ marginLeft: 10 }}
+            onClick={() => {
+              const url = `/${SystemConst.API_BASE}/opc/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
+                  style={{ marginLeft: 15 }}
+                  onClick={() => {
+                    const parms = new XMLHttpRequest();
+                    parms.open('GET', errorUrl, true);
+                    parms.responseType = 'blob';
+                    parms.onload = () => {
+                      const url = window.URL.createObjectURL(parms.response);
+                      const a = document.createElement('a');
+                      a.href = url;
+                      a.download = `${fileName}-${errMessage}`;
+                      a.click();
+                    };
+                    parms.send();
+                  }}
+                >
+                  下载
+                </a>
+              </>
+            )}
+          </div>
+        </div>
+      )}
+    </div>
+  );
+};
+const Import = (props: Props) => {
+  const { close, opcId } = props;
+
+  useEffect(() => {
+    console.log(opcId);
+  }, []);
+
+  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': {
+              opcId: opcId,
+            },
+          },
+        },
+      },
+    },
+  };
+  const SchemaField = createSchemaField({
+    components: {
+      Radio,
+      Select,
+      FormItem,
+      FormLayout,
+      FileFormat,
+      NormalUpload,
+    },
+  });
+  const form = createForm({});
+  return (
+    <Modal
+      maskClosable={false}
+      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;

+ 107 - 88
src/pages/link/Channel/Opcua/index.tsx

@@ -14,7 +14,7 @@ import {
   SearchOutlined,
   StopOutlined,
 } from '@ant-design/icons';
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useMemo, useRef, useState } from 'react';
 import { useIntl } from 'umi';
 import ChannelCard from '../channelCard';
 import { PageContainer } from '@ant-design/pro-layout';
@@ -23,10 +23,12 @@ import SaveChannel from './saveChannel';
 import SavePoint from './savePoint';
 // import Import from './import';
 import { onlyMessage } from '@/utils/util';
+import Export from './Export';
+import Import from './import';
 
 export const service = new Service('opc/client');
 
-const NewModbus = () => {
+const NewOpc = () => {
   const { minHeight } = useDomFullHeight(`.modbus`);
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
@@ -38,38 +40,43 @@ const NewModbus = () => {
   const [current, setCurrent] = useState<any>({});
   const [pointDetail, setPointDetail] = useState<any>({});
   // const [importVisible, setImportVisible] = useState<boolean>(false);
-  const [masterList, setMasterList] = useState<any>([]);
+  const [opcList, setOpcList] = useState<any>([]);
   const [filterList, setFilterList] = useState([]);
-  const masterId = useRef<string>('');
+  const opcId = useRef<string>('');
+  const [pointList, setPointList] = useState<any>([]);
+  const [exportVisible, setExportVisible] = useState<boolean>(false);
+  const [importVisible, setImportVisible] = useState<boolean>(false);
 
   const collectMap = new Map();
-  collectMap.set('running', 'success');
-  collectMap.set('error', 'error');
-  collectMap.set('stopped', 'warning');
+  collectMap.set('good', 'success');
+  collectMap.set('failed', 'error');
+  collectMap.set('bad', 'warning');
+  collectMap.set('uncertain', 'default');
+  collectMap.set('unknown', 'default');
 
   const menu = (
     <Menu>
       <Menu.Item key="1">
         <PermissionButton
-          isPermission={permission.export}
+          isPermission={permission.view}
           icon={<ExportOutlined />}
           type="default"
           onClick={() => {
-            // setExportVisible(true);
+            setExportVisible(true);
           }}
         >
-          批量导出设备
+          批量导出点位
         </PermissionButton>
       </Menu.Item>
       <Menu.Item key="2">
         <PermissionButton
-          isPermission={permission.import || true}
+          isPermission={permission.update}
           icon={<ImportOutlined />}
           onClick={() => {
-            // setImportVisible(true);
+            setImportVisible(true);
           }}
         >
-          批量导入设备
+          批量导入点位
         </PermissionButton>
       </Menu.Item>
     </Menu>
@@ -84,13 +91,12 @@ const NewModbus = () => {
       fixed: 'left',
     },
     {
-      title: '点位Id',
-      render: (record: any) => <>{record.function?.text}</>,
+      title: '点位ID',
+      dataIndex: 'opcPointId',
     },
     {
       title: '数据类型',
-      dataIndex: 'unitId',
-      search: false,
+      dataIndex: 'type',
     },
     {
       title: '当前数据',
@@ -99,28 +105,29 @@ const NewModbus = () => {
     },
     {
       title: '采集状态',
-      search: false,
-      render: (record: any) => (
+      dataIndex: 'runningState',
+      valueType: 'select',
+      valueEnum: {
+        running: { text: '采集中', status: 'running' },
+        error: { text: '失败', status: 'error' },
+        stopped: { text: '已停止', status: 'stopped' },
+      },
+      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() {},
-                  });
-                }}
-              />
-            </>
+          <Badge
+            status={collectMap.get(record.runningState?.value)}
+            text={record.runningState?.text}
+          />
+          {record.runningState?.value === 'error' && (
+            <SearchOutlined
+              style={{ color: '#1d39c4', marginLeft: 3 }}
+              onClick={() => {
+                Modal.error({
+                  title: '失败原因',
+                  content: <div>{record.errorReason}</div>,
+                });
+              }}
+            />
           )}
         </>
       ),
@@ -256,11 +263,10 @@ const NewModbus = () => {
       })
       .then((res: any) => {
         if (res.status === 200) {
-          setMasterList(res.result);
+          setOpcList(res.result);
           setFilterList(res.result);
           setActiveKey(res.result?.[0]?.id);
-          masterId.current = res.result?.[0]?.id;
-          console.log(masterId.current);
+          opcId.current = res.result?.[0]?.id;
         }
       });
   };
@@ -293,14 +299,29 @@ const NewModbus = () => {
     });
   };
 
+  const masterMemo = useMemo(
+    () => (
+      <Export
+        data={opcList}
+        close={() => {
+          setExportVisible(false);
+          actionRef.current?.reload();
+        }}
+      />
+    ),
+    [opcList],
+  );
   useEffect(() => {
-    masterId.current = activeKey;
+    opcId.current = activeKey;
     actionRef.current?.reload();
   }, [activeKey]);
 
   useEffect(() => {
     getOpc();
   }, []);
+  useEffect(() => {
+    console.log(pointList);
+  }, []);
 
   return (
     <PageContainer>
@@ -312,13 +333,13 @@ const NewModbus = () => {
                 placeholder="请输入名称"
                 allowClear
                 onSearch={(value) => {
-                  const items = masterList.filter((item: any) => item.name.match(value));
+                  const items = opcList.filter((item: any) => item.name.match(value));
                   if (value) {
                     setFilterList(items);
                     setActiveKey(items?.[0].id);
                   } else {
-                    setFilterList(masterList);
-                    setActiveKey(masterList?.[0].id);
+                    setFilterList(opcList);
+                    setActiveKey(opcList?.[0].id);
                   }
                 }}
               />
@@ -454,40 +475,36 @@ const NewModbus = () => {
                   </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' }] })
-              // }
+              request={async (params) => {
+                if (opcId.current) {
+                  const res = await service.queryPoint(opcId.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>
@@ -511,15 +528,17 @@ const NewModbus = () => {
           }}
         />
       )}
-      {/* <Import
-        data={current}
-        close={() => {
-          setImportVisible(false);
-          actionRef.current?.reload();
-        }}
-        visible={importVisible}
-      /> */}
+      {importVisible && (
+        <Import
+          opcId={activeKey}
+          close={() => {
+            setImportVisible(false);
+            actionRef.current?.reload();
+          }}
+        />
+      )}
+      {exportVisible && masterMemo}
     </PageContainer>
   );
 };
-export default NewModbus;
+export default NewOpc;

+ 86 - 14
src/pages/link/Channel/Opcua/savePoint.tsx

@@ -1,7 +1,7 @@
 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';
+import { service } from '@/pages/link/Channel/Opcua';
+import { onlyMessage } from '@/utils/util';
 
 interface Props {
   data: any;
@@ -15,12 +15,39 @@ const SavePoint = (props: Props) => {
 
   const handleSave = async () => {
     const formData = await form.validateFields();
-    console.log(formData);
+    if (props.data.id) {
+      service
+        .editPoint(props.data.id, {
+          opcUaId: props.opcId,
+          ...formData,
+        })
+        .then((res) => {
+          if (res.status === 200) {
+            onlyMessage('保存成功');
+            props.close();
+          }
+        });
+    } else {
+      service
+        .addPoint({
+          opcUaId: props.opcId,
+          ...formData,
+        })
+        .then((res) => {
+          if (res.status === 200) {
+            onlyMessage('保存成功');
+            props.close();
+          }
+        });
+    }
   };
 
   useEffect(() => {
-    console.log(dataMode);
-  }, [dataMode]);
+    console.log(props.data);
+    if (props.data.id) {
+      setDataMode(props.data.dataMode?.value);
+    }
+  }, []);
 
   return (
     <Modal
@@ -36,15 +63,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}>
@@ -82,6 +103,54 @@ const SavePoint = (props: Props) => {
           </Col>
         </Row>
         <Row gutter={[24, 24]}>
+          <Col span={24}>
+            <Form.Item
+              label="数据类型"
+              name="type"
+              required
+              rules={[{ required: true, message: '数据类型必选' }]}
+            >
+              <Select
+                placeholder="请选择数据模式"
+                onChange={(value) => {
+                  setDataMode(value);
+                  form.setFieldsValue({
+                    interval: '',
+                  });
+                }}
+              >
+                <Select.Option value="Boolean" key={'Boolean'}>
+                  Boolean
+                </Select.Option>
+                <Select.Option value="Byte" key={'Byte'}>
+                  Byte
+                </Select.Option>
+                <Select.Option value="Short" key={'Short'}>
+                  Short
+                </Select.Option>
+                <Select.Option value="Integer" key={'Integer'}>
+                  Boolean
+                </Select.Option>
+                <Select.Option value="Long" key={'Long'}>
+                  Long
+                </Select.Option>
+                <Select.Option value="LLong" key={'LLong'}>
+                  LLong
+                </Select.Option>
+                <Select.Option value="Double" key={'Double'}>
+                  Double
+                </Select.Option>
+                <Select.Option value="String" key={'String'}>
+                  String
+                </Select.Option>
+                <Select.Option value="DateTime" key={'DateTime'}>
+                  DateTime
+                </Select.Option>
+              </Select>
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={[24, 24]}>
           <Col span={12}>
             <Form.Item
               label="数据模式"
@@ -103,6 +172,9 @@ const SavePoint = (props: Props) => {
                 placeholder="请选择数据模式"
                 onChange={(value) => {
                   setDataMode(value);
+                  form.setFieldsValue({
+                    interval: '',
+                  });
                 }}
               >
                 <Select.Option value="sub" key={'sub'}>

+ 6 - 1
src/pages/link/Channel/Opcua/service.ts

@@ -66,7 +66,7 @@ class Service extends BaseService<OpaUa> {
       method: 'POST',
       data: params,
     });
-  editPoint = (params: any, id: string) =>
+  editPoint = (id: string, params: any) =>
     request(`/${SystemConst.API_BASE}/opc/point/${id}`, {
       method: 'PUT',
       data: params,
@@ -90,6 +90,11 @@ class Service extends BaseService<OpaUa> {
       method: 'POST',
       data,
     });
+  queryPoint = (opcUaId: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/opc/point/${opcUaId}/_query`, {
+      method: 'POST',
+      data,
+    });
 }
 
 export default Service;

+ 3 - 0
src/pages/rule-engine/Scene/Save/action/device/ConditionalFiltering.tsx

@@ -267,6 +267,9 @@ export default (props: ConditionalFilteringProps) => {
                       },
                     ]);
                   } else {
+                    // console.log(2222222222)
+                    // console.log(props.form.getFieldValue([props.name, 'terms', 0, 'value', 'value']))
+                    // props.form.resetFields([props.name, 'terms', 0, 'value', 'value'])
                     props.form.setFields([
                       {
                         name: [props.name, 'terms', 0, 'value', 'value'],