Selaa lähdekoodia

feat: opc 设备绑定

wzyyy 4 vuotta sitten
vanhempi
commit
46d17b0362

+ 1 - 0
package.json

@@ -62,6 +62,7 @@
     "@ant-design/pro-descriptions": "^1.6.8",
     "@ant-design/pro-form": "^1.18.3",
     "@ant-design/pro-layout": "^6.27.2",
+    "@ant-design/pro-list": "^1.21.61",
     "@formily/antd": "2.1.2",
     "@formily/core": "2.1.2",
     "@formily/json-schema": "2.1.2",

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

@@ -0,0 +1,7 @@
+.list {
+  :global {
+    .ant-pro-card-body {
+      padding: 0 0 0 0;
+    }
+  }
+}

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

@@ -0,0 +1,125 @@
+import { Modal } from '@/components';
+import SearchComponent from '@/components/SearchComponent';
+import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { Badge, message } from 'antd';
+import { useEffect, useState, useRef } from 'react';
+import { service } from '@/pages/link/Channel/Opcua';
+import moment from 'moment';
+
+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',
+    },
+    {
+      title: '设备名称',
+      dataIndex: 'name',
+    },
+    {
+      title: '产品名称',
+      dataIndex: 'productName',
+    },
+    {
+      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)} />,
+    },
+  ];
+
+  const save = () => {
+    const params = bindDevice.map((item: any) => ({
+      opcUaId: props.id,
+      deviceId: item.id,
+      deviceName: item.name,
+      productId: item.productId,
+      productName: item.productName,
+    }));
+    console.log(params);
+    service.bind(params).then((res) => {
+      if (res.status === 200) {
+        message.success('绑定成功');
+        props.close();
+      }
+    });
+  };
+
+  useEffect(() => {
+    console.log(props.id);
+  }, []);
+
+  return (
+    <Modal
+      title={'绑定设备'}
+      maskClosable={false}
+      visible
+      onCancel={props.close}
+      onOk={() => {
+        save();
+      }}
+      width={1300}
+      permissionCode={'link/Channel/Opcua'}
+      permission={['add', 'edit', 'view']}
+    >
+      <SearchComponent
+        field={columns}
+        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}
+        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;

+ 176 - 1
src/pages/link/Channel/Opcua/Access/index.tsx

@@ -1,6 +1,181 @@
+import PermissionButton from '@/components/PermissionButton';
 import { PageContainer } from '@ant-design/pro-layout';
+import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { Badge, Card, Col, Row, Popconfirm, message } from 'antd';
+import { useIntl, useLocation } from 'umi';
+import { useEffect, useRef, useState } from 'react';
+import { DisconnectOutlined, PlusOutlined } 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 ProList from '@ant-design/pro-list';
+// import styles from './index.less'
 
 const Access = () => {
-  return <PageContainer>Access</PageContainer>;
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const location = useLocation<string>();
+  // const [param, setParam] = useState({});
+  const [opcUaId, setOpcUaId] = useState<any>('');
+  const { permission } = PermissionButton.usePermission('link/Channel/Opcua');
+  const [deviceVisiable, setDeviceVisiable] = useState<boolean>(false);
+  const [bindList, setBindList] = useState<any>([]);
+
+  const columns: ProColumns<OpaUa>[] = [
+    {
+      title: '属性ID',
+      dataIndex: 'name',
+    },
+    {
+      title: '功能码',
+      dataIndex: '1',
+    },
+    {
+      title: '读取起始位置',
+      dataIndex: '2',
+    },
+    {
+      title: '读取长度',
+      dataIndex: '3',
+    },
+    {
+      title: '值',
+      dataIndex: '4',
+    },
+    {
+      title: '状态',
+      dataIndex: 'state',
+      renderText: (state) => (
+        <Badge text={state?.text} status={state?.value === 'disabled' ? 'error' : 'success'} />
+      ),
+    },
+  ];
+
+  const getBindList = (params: any) => {
+    service.getBindList(params).then((res) => {
+      if (res.status === 200) {
+        setBindList(res.result);
+      }
+    });
+  };
+
+  useEffect(() => {
+    const item = new URLSearchParams(location.search);
+    if (item.get('id')) {
+      setOpcUaId(opcUaId.get('id'));
+      getBindList(
+        encodeQuery({
+          terms: {
+            opcUaId: opcUaId.get('id'),
+          },
+        }),
+      );
+    }
+  }, []);
+
+  return (
+    <PageContainer>
+      <Card>
+        <Row>
+          <Col span={4}>
+            <PermissionButton
+              onClick={() => {
+                setDeviceVisiable(true);
+              }}
+              isPermission={permission.add}
+              key="add"
+              icon={<PlusOutlined />}
+              type="dashed"
+              style={{ width: '100%', marginTop: 16 }}
+            >
+              绑定设备
+            </PermissionButton>
+            <ProList
+              rowKey="id"
+              dataSource={bindList}
+              showActions="hover"
+              showExtra="hover"
+              metas={{
+                title: {
+                  dataIndex: 'name',
+                },
+                actions: {
+                  render: (text, row) => [
+                    <Popconfirm
+                      title="确认解绑该设备嘛?"
+                      onConfirm={() => {
+                        console.log(row);
+                        service.unbind([row.id], opcUaId).then((res) => {
+                          if (res.status === 200) {
+                            message.success('解绑成功');
+                            getBindList(
+                              encodeQuery({
+                                terms: {
+                                  opcUaId: opcUaId,
+                                },
+                              }),
+                            );
+                          }
+                        });
+                      }}
+                      okText="Yes"
+                      cancelText="No"
+                    >
+                      <DisconnectOutlined />
+                    </Popconfirm>,
+                  ],
+                },
+              }}
+            />
+          </Col>
+          <Col span={20}>
+            <ProTable<OpaUa>
+              actionRef={actionRef}
+              // params={param}
+              columns={columns}
+              rowKey="id"
+              search={false}
+              headerTitle={
+                <PermissionButton
+                  onClick={() => {
+                    // setMode('add');
+                    // setVisible(true);
+                    // setCurrent({});
+                  }}
+                  isPermission={permission.add}
+                  key="add"
+                  icon={<PlusOutlined />}
+                  type="primary"
+                >
+                  {intl.formatMessage({
+                    id: 'pages.data.option.add',
+                    defaultMessage: '新增',
+                  })}
+                </PermissionButton>
+              }
+              // request={async (params) =>
+              //   service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+              // }
+            />
+          </Col>
+        </Row>
+      </Card>
+      {deviceVisiable && (
+        <BindDevice
+          id={opcUaId}
+          close={() => {
+            setDeviceVisiable(false);
+            getBindList(
+              encodeQuery({
+                terms: {
+                  opcUaId: opcUaId,
+                },
+              }),
+            );
+          }}
+        />
+      )}
+    </PageContainer>
+  );
 };
 export default Access;

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

@@ -244,7 +244,7 @@ const Save = (props: Props) => {
       onCancel={props.close}
       onOk={save}
       width="35vw"
-      permissionCode={'system/Relationship'}
+      permissionCode={'link/Channel/Opcua'}
       permission={['add', 'edit']}
     >
       <Form form={form} layout="vertical">

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

@@ -21,6 +21,27 @@ class Service extends BaseService<OpaUa> {
       method: 'GET',
       params,
     });
+  getDevice = (params?: any) =>
+    request(`${SystemConst.API_BASE}/device-instance/_query`, {
+      method: 'POST',
+      data: params,
+    });
+  //绑定设备
+  bind = (params: any) =>
+    request(`${SystemConst.API_BASE}/opc/device-bind/batch/_create`, {
+      method: 'POST',
+      data: params,
+    });
+  getBindList = (params: any) =>
+    request(`${SystemConst.API_BASE}/opc/device-bind/device-details/_query/no-paging`, {
+      method: 'GET',
+      params,
+    });
+  unbind = (params: any, opcUaId: string) =>
+    request(`${SystemConst.API_BASE}/opc/device-bind/batch/${opcUaId}/_delete`, {
+      method: 'POST',
+      data: params,
+    });
 }
 
 export default Service;

+ 109 - 0
yarn.lock

@@ -121,6 +121,18 @@
     classnames "^2.2.6"
     omit.js "^2.0.2"
 
+"@ant-design/pro-card@1.20.4":
+  version "1.20.4"
+  resolved "https://registry.yarnpkg.com/@ant-design/pro-card/-/pro-card-1.20.4.tgz#b370bd3b0240628b1deb4a02319829861066e914"
+  integrity sha512-XWD5bInxS+G5Bnvg9L7uSDcIyEgKpJiI9CFoZgomSbYU+IctK+2PEYGEgFqS0OuXM21KPNglTJ3L/SSm57KdcQ==
+  dependencies:
+    "@ant-design/icons" "^4.2.1"
+    "@ant-design/pro-utils" "1.41.2"
+    "@babel/runtime" "^7.16.3"
+    classnames "^2.2.6"
+    omit.js "^2.0.2"
+    rc-util "^5.4.0"
+
 "@ant-design/pro-card@^1.16.2":
   version "1.19.2"
   resolved "https://registry.yarnpkg.com/@ant-design/pro-card/-/pro-card-1.19.2.tgz#439d880bfa132d3974df89b976ac55a974938b08"
@@ -213,6 +225,23 @@
     react-color "2.19.3"
     swr "^1.2.0"
 
+"@ant-design/pro-field@1.34.10":
+  version "1.34.10"
+  resolved "https://registry.yarnpkg.com/@ant-design/pro-field/-/pro-field-1.34.10.tgz#9fe8f051808b8ca3ac78362a40fc9e4e7c06cd70"
+  integrity sha512-lKVPAH/lplRvXL8D9w6jF+31akfbKklXWi9NU4/4pt1VmooAFp1NAyhokjgDMk7Cr8UA1r3P4DZvv21Ujg1DJg==
+  dependencies:
+    "@ant-design/icons" "^4.2.1"
+    "@ant-design/pro-provider" "1.6.5"
+    "@ant-design/pro-utils" "1.41.2"
+    "@babel/runtime" "^7.16.3"
+    "@chenshuai2144/sketch-color" "^1.0.8"
+    classnames "^2.2.6"
+    lodash.tonumber "^4.0.3"
+    moment "^2.27.0"
+    omit.js "^2.0.2"
+    rc-util "^5.4.0"
+    swr "^1.2.0"
+
 "@ant-design/pro-field@1.34.2":
   version "1.34.2"
   resolved "https://registry.yarnpkg.com/@ant-design/pro-field/-/pro-field-1.34.2.tgz#b0a0eb1fbeac1c18cbc2d6738e5f7c526324b5b3"
@@ -284,6 +313,25 @@
     use-json-comparison "^1.0.5"
     use-media-antd-query "^1.0.6"
 
+"@ant-design/pro-form@1.67.3":
+  version "1.67.3"
+  resolved "https://registry.yarnpkg.com/@ant-design/pro-form/-/pro-form-1.67.3.tgz#62b96cdabe9ec2f3140f0246c30e4dcb82b06d48"
+  integrity sha512-axMVFBLUeSDvPIu0gJX96flKO8KuIqRMWEuX6o0jWqAiK36F8blKj6i9UZofxhn9nSnqwQsNsWSOuTbHyOB/VA==
+  dependencies:
+    "@ant-design/icons" "^4.2.1"
+    "@ant-design/pro-field" "1.34.10"
+    "@ant-design/pro-provider" "1.6.5"
+    "@ant-design/pro-utils" "1.41.2"
+    "@babel/runtime" "^7.16.3"
+    "@umijs/use-params" "^1.0.9"
+    classnames "^2.2.6"
+    lodash "^4.17.21"
+    omit.js "^2.0.2"
+    rc-resize-observer "^1.1.0"
+    rc-util "^5.0.6"
+    use-json-comparison "^1.0.5"
+    use-media-antd-query "^1.0.6"
+
 "@ant-design/pro-layout@^6.27.2":
   version "6.34.9"
   resolved "https://registry.yarnpkg.com/@ant-design/pro-layout/-/pro-layout-6.34.9.tgz#d84178595eaf1fd03c707d1e3b6779bb6266305c"
@@ -308,6 +356,23 @@
     use-media-antd-query "^1.0.6"
     warning "^4.0.3"
 
+"@ant-design/pro-list@^1.21.61":
+  version "1.21.61"
+  resolved "https://registry.yarnpkg.com/@ant-design/pro-list/-/pro-list-1.21.61.tgz#cfc22c3c8d5819c7665e755d964d49e1b7fb6811"
+  integrity sha512-fOI6jKmxFDqymsl877yPACAdwYvSLepRAjGE9netuSgjP6etsTjRg3grL8C/tsh3SGQ4yJoGAOEsPFRDBaTjsQ==
+  dependencies:
+    "@ant-design/icons" "^4.0.0"
+    "@ant-design/pro-card" "1.20.4"
+    "@ant-design/pro-field" "1.34.10"
+    "@ant-design/pro-table" "2.74.3"
+    "@babel/runtime" "^7.16.3"
+    classnames "^2.2.6"
+    moment "^2.24.0"
+    rc-resize-observer "^1.0.0"
+    rc-util "^4.19.0"
+    unstated-next "^1.1.0"
+    use-media-antd-query "1.0.6"
+
 "@ant-design/pro-provider@1.4.15":
   version "1.4.15"
   resolved "https://registry.yarnpkg.com/@ant-design/pro-provider/-/pro-provider-1.4.15.tgz#2786c65d968bd0ed3cccebe46d326e9e6aaa3cb5"
@@ -333,6 +398,15 @@
     rc-util "^5.0.1"
     swr "^1.2.0"
 
+"@ant-design/pro-provider@1.6.5":
+  version "1.6.5"
+  resolved "https://registry.yarnpkg.com/@ant-design/pro-provider/-/pro-provider-1.6.5.tgz#e30af6eec4602bd33b3cec894e918ea347f266ee"
+  integrity sha512-83hy+q5vCQLRT7QY/3Wo4YL6eAWWi+Svqc7B7Mxw73WT7yPqzhOaRqLyyytPtQrSCT5ldJsqtfWgJBtWIkoNYw==
+  dependencies:
+    "@babel/runtime" "^7.16.3"
+    rc-util "^5.0.1"
+    swr "^1.2.0"
+
 "@ant-design/pro-skeleton@1.0.5":
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/@ant-design/pro-skeleton/-/pro-skeleton-1.0.5.tgz#d66234571cce7c4b195d0cfe6b951391ef42dcc7"
@@ -341,6 +415,27 @@
     "@babel/runtime" "^7.16.3"
     use-media-antd-query "^1.0.6"
 
+"@ant-design/pro-table@2.74.3":
+  version "2.74.3"
+  resolved "https://registry.yarnpkg.com/@ant-design/pro-table/-/pro-table-2.74.3.tgz#4d322ca73f2fc7abfdc51b4a2afe91f6a97b1b1c"
+  integrity sha512-XZOzJmjaeJ/iF2Z0eyhAiGeVF87aWAzI2uD3dTU8wAvfCb3Q2ANfMnj8ZeYBmtAGbCinVSm4b4yYxEXORc/43w==
+  dependencies:
+    "@ant-design/icons" "^4.1.0"
+    "@ant-design/pro-card" "1.20.4"
+    "@ant-design/pro-field" "1.34.10"
+    "@ant-design/pro-form" "1.67.3"
+    "@ant-design/pro-provider" "1.6.5"
+    "@ant-design/pro-utils" "1.41.2"
+    "@babel/runtime" "^7.16.3"
+    classnames "^2.2.6"
+    moment "^2.24.0"
+    omit.js "^2.0.2"
+    rc-util "^5.0.1"
+    react-sortable-hoc "^2.0.0"
+    unstated-next "^1.1.0"
+    use-json-comparison "^1.0.5"
+    use-media-antd-query "^1.1.0"
+
 "@ant-design/pro-utils@1.19.5":
   version "1.19.5"
   resolved "https://registry.yarnpkg.com/@ant-design/pro-utils/-/pro-utils-1.19.5.tgz#df49b3f0e99fe4adcc896ea37e28abbff31838bc"
@@ -383,6 +478,20 @@
     react-sortable-hoc "^2.0.0"
     swr "^1.2.0"
 
+"@ant-design/pro-utils@1.41.2":
+  version "1.41.2"
+  resolved "https://registry.yarnpkg.com/@ant-design/pro-utils/-/pro-utils-1.41.2.tgz#a0c76c7a1201d26e385e7b6b56fbf708306bcd4a"
+  integrity sha512-wVDMwFrLVP0Ng7yDUGrZqot0zCdN1wtpVtGdF6KTZzjMwZ5glWBQWWZ2oFNFqhgRZ9QlojkNWISQl4NnDS/4LA==
+  dependencies:
+    "@ant-design/icons" "^4.3.0"
+    "@ant-design/pro-provider" "1.6.5"
+    "@babel/runtime" "^7.16.3"
+    classnames "^2.2.6"
+    moment "^2.27.0"
+    rc-util "^5.0.6"
+    react-sortable-hoc "^2.0.0"
+    swr "^1.2.0"
+
 "@ant-design/react-slick@~0.28.1":
   version "0.28.4"
   resolved "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-0.28.4.tgz#8b296b87ad7c7ae877f2a527b81b7eebd9dd29a9"