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

feat(device): run state 、log、child device

Next sc
Lind 3 лет назад
Родитель
Сommit
1891c106a3

+ 158 - 0
src/pages/device/Instance/Detail/ChildDevice/BindChildDevice/index.tsx

@@ -0,0 +1,158 @@
+import { Badge, Button, Modal } from "antd";
+import type { DeviceInstance } from '@/pages/device/Instance/typings';
+import SearchComponent from "@/components/SearchComponent";
+import type { ActionType, ProColumns } from "@jetlinks/pro-table";
+import ProTable from "@jetlinks/pro-table";
+import { useRef, useState } from "react";
+import { InstanceModel, service } from '@/pages/device/Instance';
+import { useIntl } from "umi";
+import moment from "moment";
+
+
+interface Props {
+    visible: boolean;
+    data: Partial<DeviceInstance>;
+    onCancel: () => void;
+}
+const BindChildDevice = (props: Props) => {
+    const { visible } = props;
+    const intl = useIntl();
+    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 actionRef = useRef<ActionType>();
+    const [searchParams, setSearchParams] = useState<any>({});
+    const [bindKeys, setBindKeys] = useState<any[]>([]);
+
+    const columns: ProColumns<DeviceInstance>[] = [
+        {
+            dataIndex: 'index',
+            valueType: 'indexBorder',
+            width: 48,
+        },
+        {
+            title: 'ID',
+            dataIndex: 'id'
+        },
+        {
+            title: '设备名称',
+            dataIndex: 'name'
+        },
+        {
+            title: '所属产品',
+            dataIndex: 'productName'
+        },
+        {
+            title: '注册时间',
+            dataIndex: 'registryTime',
+            width: '200px',
+            render: (text: any) => (!!text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : '/'),
+            sorter: true,
+        },
+        {
+            title: '状态',
+            dataIndex: 'state',
+            renderText: (record) =>
+                record ? <Badge status={statusMap.get(record.value)} text={record.text} /> : '',
+            valueType: 'select',
+            valueEnum: {
+                'notActive': {
+                    text: intl.formatMessage({
+                        id: 'pages.device.instance.status.notActive',
+                        defaultMessage: '未启用',
+                    }),
+                    value: 'notActive',
+                },
+                'offline': {
+                    text: intl.formatMessage({
+                        id: 'pages.device.instance.status.offLine',
+                        defaultMessage: '离线',
+                    }),
+                    value: 'offline',
+                },
+                'online': {
+                    text: intl.formatMessage({
+                        id: 'pages.device.instance.status.onLine',
+                        defaultMessage: '在线',
+                    }),
+                    value: 'online',
+                },
+            }
+        }
+    ];
+
+    const submitBtn = async () => {
+        const resp = await service.bindDevice(InstanceModel.detail.id!, bindKeys)
+        if (resp.status === 200) {
+            props.onCancel();
+            setBindKeys([])
+            actionRef.current?.reset?.();
+        }
+    }
+
+    return (
+        <Modal
+            title="绑定子设备"
+            visible={visible}
+            width={1000}
+            onOk={() => { submitBtn() }}
+            onCancel={() => {
+                props.onCancel();
+                setBindKeys([])
+            }}
+            footer={[
+                <Button key="back" onClick={() => {
+                    props.onCancel()
+                    setBindKeys([])
+                    actionRef.current?.reset?.();
+                }}>
+                    取消
+                </Button>,
+                <Button disabled={!(bindKeys.length > 0)} key="submit" type="primary" onClick={() => { submitBtn() }}>
+                    确认
+                </Button>
+            ]}
+        >
+            <SearchComponent<DeviceInstance>
+                field={[...columns]}
+                target="child-device-bind"
+                pattern={'simple'}
+                defaultParam={[{ column: 'parentId$isnull', value: '1' }]}
+                onSearch={(param) => {
+                    actionRef.current?.reset?.();
+                    setSearchParams(param);
+                }}
+                onReset={() => {// 重置分页及搜索参数
+                    actionRef.current?.reset?.();
+                    setSearchParams({});
+                }}
+            />
+            <ProTable<DeviceInstance>
+                search={false}
+                columns={columns}
+                size="small"
+                rowSelection={{
+                    selectedRowKeys: bindKeys,
+                    onChange: (selectedRowKeys, selectedRows) => {
+                        setBindKeys(selectedRows.map((item) => item.id))
+                    },
+                }}
+                actionRef={actionRef}
+                params={searchParams}
+                rowKey="id"
+                toolBarRender={false}
+                pagination={{
+                    pageSize: 10,
+                }}
+                request={(params) => service.query({ ...params })}
+            />
+        </Modal>
+    );
+};
+export default BindChildDevice

+ 193 - 0
src/pages/device/Instance/Detail/ChildDevice/index.tsx

@@ -0,0 +1,193 @@
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import type { LogItem } from '@/pages/device/Instance/Detail/Log/typings';
+import { Badge, Button, Card, message, Popconfirm, Tooltip } from 'antd';
+import { DisconnectOutlined, SearchOutlined } from '@ant-design/icons';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { InstanceModel, service } from '@/pages/device/Instance';
+import { useRef, useState } from 'react';
+import SearchComponent from '@/components/SearchComponent';
+import BindChildDevice from './BindChildDevice'
+import moment from 'moment';
+import { Link } from 'umi';
+
+const ChildDevice = () => {
+    const intl = useIntl();
+    const [visible, setVisible] = useState<boolean>(false)
+    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 actionRef = useRef<ActionType>();
+    const [searchParams, setSearchParams] = useState<any>({});
+    const [bindKeys, setBindKeys] = useState<any[]>([]);
+
+    const unBindSingleDevice = async (id: string) => {
+        const resp = await service.unbindDevice(InstanceModel.detail.id!, id, {})
+        if (resp.status === 200) {
+            actionRef.current?.reset?.();
+            message.success('操作成功!')
+        }
+    }
+
+    const columns: ProColumns<LogItem>[] = [
+        {
+            dataIndex: 'index',
+            valueType: 'indexBorder',
+            width: 48,
+        },
+        {
+            title: 'ID',
+            dataIndex: 'id'
+        },
+        {
+            title: '设备名称',
+            dataIndex: 'name'
+        },
+        {
+            title: '所属产品',
+            dataIndex: 'productName'
+        },
+        {
+            title: '注册时间',
+            dataIndex: 'registryTime',
+            width: '200px',
+            render: (text: any) => (!!text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : '/'),
+            sorter: true,
+        },
+        {
+            title: '状态',
+            dataIndex: 'state',
+            renderText: (record) =>
+                record ? <Badge status={statusMap.get(record.value)} text={record.text} /> : '',
+            valueType: 'select',
+            valueEnum: {
+                'notActive': {
+                    text: intl.formatMessage({
+                        id: 'pages.device.instance.status.notActive',
+                        defaultMessage: '未启用',
+                    }),
+                    value: 'notActive',
+                },
+                'offline': {
+                    text: intl.formatMessage({
+                        id: 'pages.device.instance.status.offLine',
+                        defaultMessage: '离线',
+                    }),
+                    value: 'offline',
+                },
+                'online': {
+                    text: intl.formatMessage({
+                        id: 'pages.device.instance.status.onLine',
+                        defaultMessage: '在线',
+                    }),
+                    value: 'online',
+                },
+            }
+        },
+        {
+            title: intl.formatMessage({
+                id: 'pages.data.option',
+                defaultMessage: '操作',
+            }),
+            valueType: 'option',
+            align: 'center',
+            width: 200,
+            render: (text, record) => [
+                <Link
+                    to={`/device/instance/detail/${record.id}`}
+                    key="link"
+                >
+                    <Tooltip
+                        title={intl.formatMessage({
+                            id: 'pages.data.option.detail',
+                            defaultMessage: '查看',
+                        })}
+                        key={'detail'}
+                    >
+                        <SearchOutlined />
+                    </Tooltip>
+                </Link>,
+                <a key="unbind">
+                    <Popconfirm
+                        onConfirm={() => {
+                            unBindSingleDevice(record.id)
+                        }}
+                        title={'确认解绑吗?'}
+                    >
+                        <DisconnectOutlined />
+                    </Popconfirm>
+                </a>
+            ],
+        },
+    ];
+
+    return (
+        <Card>
+            <SearchComponent<LogItem>
+                field={[...columns]}
+                target="child-device"
+                pattern={'simple'}
+                defaultParam={[{ column: 'parentId', value: InstanceModel?.detail?.id || '', termType: 'eq' }]}
+                onSearch={(param) => {
+                    actionRef.current?.reset?.();
+                    setSearchParams(param);
+                }}
+                onReset={() => {// 重置分页及搜索参数
+                    actionRef.current?.reset?.();
+                    setSearchParams({});
+                }}
+            />
+            <ProTable<LogItem>
+                search={false}
+                columns={columns}
+                size="small"
+                actionRef={actionRef}
+                params={searchParams}
+                rowKey="id"
+                rowSelection={{
+                    selectedRowKeys: bindKeys,
+                    onChange: (selectedRowKeys, selectedRows) => {
+                        setBindKeys(selectedRows.map((item) => item.id))
+                    },
+                }}
+                toolBarRender={() => [
+                    <Button
+                        onClick={() => {
+                            setVisible(true)
+                            actionRef.current?.reset?.();
+                        }}
+                        key="bind"
+                        type="primary"
+                    >
+                        绑定
+                    </Button>,
+                    <Popconfirm
+                        key="unbind"
+                        onConfirm={async () => {
+                            const resp = await service.unbindBatchDevice(InstanceModel.detail.id!, bindKeys)
+                            if (resp.status === 200) {
+                                message.success('操作成功!')
+                                setBindKeys([])
+                                actionRef.current?.reset?.();
+                            }
+                        }}
+                        title={'确认解绑吗?'}
+                    >
+                        <Button>批量解绑</Button>
+                    </Popconfirm>
+                ]}
+                pagination={{
+                    pageSize: 10,
+                }}
+                request={(params) => service.query(params)}
+            />
+            <BindChildDevice visible={visible} data={{}} onCancel={() => { setVisible(false) }} />
+        </Card>
+    );
+}
+export default ChildDevice;

+ 12 - 0
src/pages/device/Instance/Detail/ChildDevice/typings.d.ts

@@ -0,0 +1,12 @@
+export type LogItem = {
+  id: string;
+  deviceId: string;
+  productId: string;
+  timestamp: number;
+  type: {
+    text: string;
+    value: string;
+  };
+  content: string;
+  createTime: number;
+};

+ 49 - 30
src/pages/device/Instance/Detail/Log/index.tsx

@@ -1,22 +1,26 @@
-import type { ProColumns } from '@jetlinks/pro-table';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import type { LogItem } from '@/pages/device/Instance/Detail/Log/typings';
-import { Modal, Tooltip } from 'antd';
-import { EyeOutlined } from '@ant-design/icons';
+import { Card, Modal, Tooltip } from 'antd';
+import { SearchOutlined } from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { InstanceModel, service } from '@/pages/device/Instance';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
+import SearchComponent from '@/components/SearchComponent';
 
 const Log = () => {
   const intl = useIntl();
 
   const [type, setType] = useState<any>({});
+  const actionRef = useRef<ActionType>();
+  const [searchParams, setSearchParams] = useState<any>({});
+
   useEffect(() => {
     service.getLogType().then((resp) => {
       if (resp.status === 200) {
-        const list = (resp.result as { text: string; value: string }[]).reduce(
+        const list = (resp.result as { text: string; type: string }[]).reduce(
           (previousValue, currentValue) => {
-            previousValue[currentValue.value] = currentValue;
+            previousValue[currentValue.type] = currentValue;
             return previousValue;
           },
           {},
@@ -25,6 +29,7 @@ const Log = () => {
       }
     });
   }, []);
+
   const columns: ProColumns<LogItem>[] = [
     {
       dataIndex: 'index',
@@ -35,7 +40,8 @@ const Log = () => {
       title: '类型',
       dataIndex: 'type',
       renderText: (text) => text.text,
-      valueEnum: type,
+      valueType: 'select',
+      valueEnum: type
     },
     {
       title: '时间',
@@ -46,17 +52,11 @@ const Log = () => {
       hideInSearch: true,
     },
     {
-      title: '时间',
-      dataIndex: 'timestamp',
-      defaultSortOrder: 'descend',
-      valueType: 'dateTimeRange',
-      sorter: true,
-      hideInTable: true,
-      search: {
-        transform: (value) => ({
-          timestamp$BTW: value.toString(),
-        }),
-      },
+      title: '内容',
+      dataIndex: 'content',
+      valueType: 'option',
+      ellipsis: true,
+      render: (text, record) => <span>{String(record.content)}</span>
     },
     {
       title: intl.formatMessage({
@@ -79,7 +79,7 @@ const Log = () => {
             onClick={() => Modal.info({ title: '详细信息', content: <pre>{content}</pre> })}
           >
             <Tooltip title="查看">
-              <EyeOutlined />
+              <SearchOutlined />
             </Tooltip>
           </a>,
         ];
@@ -88,17 +88,36 @@ const Log = () => {
   ];
 
   return (
-    <ProTable<LogItem>
-      columns={columns}
-      defaultParams={{
-        deviceId: InstanceModel.detail.id,
-      }}
-      rowKey="id"
-      pagination={{
-        pageSize: 10,
-      }}
-      request={(params) => service.queryLog(InstanceModel.detail.id!, params)}
-    />
+    <Card>
+      <SearchComponent<LogItem>
+        field={[...columns]}
+        target="logs"
+        pattern={'simple'}
+        onSearch={(param) => {
+          actionRef.current?.reset?.();
+          setSearchParams(param);
+        }}
+        onReset={() => {// 重置分页及搜索参数
+          actionRef.current?.reset?.();
+          setSearchParams({});
+        }}
+      />
+      <ProTable<LogItem>
+        search={false}
+        columns={columns}
+        size="small"
+        actionRef={actionRef}
+        params={searchParams}
+        toolBarRender={false}
+        rowKey="id"
+        pagination={{
+          pageSize: 10,
+        }}
+        request={async (params) => {
+          return service.queryLog(InstanceModel.detail.id!, params)
+        }}
+      />
+    </Card>
   );
 };
 export default Log;

+ 0 - 5
src/pages/device/Instance/Detail/MetadataLog/columns.ts

@@ -3,11 +3,6 @@ import moment from 'moment';
 
 const columns: ProColumns<MetadataLogData>[] = [
   {
-    dataIndex: 'index',
-    valueType: 'indexBorder',
-    width: 48,
-  },
-  {
     dataIndex: 'timestamp',
     title: '时间',
     sorter: true,

+ 0 - 82
src/pages/device/Instance/Detail/Running/Event.tsx

@@ -1,82 +0,0 @@
-import { SyncOutlined, UnorderedListOutlined } from '@ant-design/icons';
-import { Badge, Divider, Tooltip } from 'antd';
-import ProCard from '@ant-design/pro-card';
-import type { EventMetadata, ObserverMetadata } from '@/pages/device/Product/typings';
-import { useEffect, useRef, useState } from 'react';
-import { service } from '@/pages/device/Instance';
-import { useParams } from 'umi';
-import EventLog from '@/pages/device/Instance/Detail/MetadataLog/Event';
-
-interface Props {
-  data: Partial<EventMetadata> & ObserverMetadata;
-}
-
-const eventLevel = new Map();
-eventLevel.set('ordinary', <Badge status="processing" text="普通" />);
-eventLevel.set('warn', <Badge status="warning" text="警告" />);
-eventLevel.set('urgent', <Badge status="error" text="紧急" />);
-
-const Event = (props: Props) => {
-  const { data } = props;
-  const params = useParams<{ id: string }>();
-
-  const [count, setCount] = useState<number>(0);
-  const cacheCount = useRef<number>(count);
-
-  const [loading, setLoading] = useState<boolean>(false);
-  const initCount = () => {
-    setLoading(true);
-    if (data.id) {
-      service
-        .getEventCount(params.id, data.id, {
-          format: true,
-          pageSize: 1,
-        })
-        .then((resp) => {
-          if (resp.status === 200) {
-            setCount(resp.result?.total);
-            cacheCount.current = resp.result?.total;
-          }
-        })
-        .finally(() => setLoading(false));
-    }
-  };
-  useEffect(() => {
-    initCount();
-    data.subscribe((payload: unknown) => {
-      if (payload) {
-        cacheCount.current = cacheCount.current + 1;
-        setCount(cacheCount.current);
-      }
-    });
-  }, [data.id, params.id]);
-  const [visible, setVisible] = useState<boolean>(false);
-
-  return (
-    <ProCard
-      title={`${data.name}: ${count}`}
-      extra={
-        <>
-          <SyncOutlined onClick={initCount} />
-          <Divider type="vertical" />
-          <Tooltip placement="top" title="详情">
-            <UnorderedListOutlined
-              onClick={() => {
-                setVisible(true);
-              }}
-            />
-          </Tooltip>
-        </>
-      }
-      loading={loading}
-      layout="center"
-      bordered
-      headerBordered
-      colSpan={{ xs: 12, sm: 8, md: 6, lg: 6, xl: 6 }}
-    >
-      <div style={{ height: 60 }}>{eventLevel.get(data.expands?.level || 'warn')}</div>
-      <EventLog visible={visible} close={() => setVisible(false)} data={data} />
-    </ProCard>
-  );
-};
-export default Event;

+ 129 - 0
src/pages/device/Instance/Detail/Running/Event/index.tsx

@@ -0,0 +1,129 @@
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { service } from '@/pages/device/Instance';
+import { useParams } from 'umi';
+import type { EventMetadata } from '@/pages/device/Product/typings';
+import SearchComponent from '@/components/SearchComponent';
+import moment from 'moment';
+import { Form, Modal } from 'antd';
+import { SearchOutlined } from '@ant-design/icons';
+import { useRef, useState } from 'react';
+import MonacoEditor from 'react-monaco-editor';
+interface Props {
+  data: Partial<EventMetadata>;
+}
+
+const EventLog = (props: Props) => {
+  const params = useParams<{ id: string }>();
+  const { data } = props;
+  const actionRef = useRef<ActionType>();
+  const [searchParams, setSearchParams] = useState<any>({});
+
+  const columns: ProColumns<MetadataLogData>[] = [
+    {
+      dataIndex: 'timestamp',
+      title: '时间',
+      sorter: true,
+      renderText: (text: string) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+      dataIndex: 'option',
+      title: '操作',
+      render: (text, record) => [
+        <a
+          key={'option'}
+          onClick={() => {
+            for (const i in record) {
+              if (i.indexOf('_format') != -1) {
+                delete record[i];
+              }
+            }
+            Modal.info({
+              title: '详情',
+              width: 850,
+              content: (
+                <Form.Item wrapperCol={{ span: 22 }} labelCol={{ span: 2 }} label={data.name}>
+                  <MonacoEditor
+                    height={350}
+                    theme="vs"
+                    language="json"
+                    value={JSON.stringify(record, null, 2)}
+                  />
+                </Form.Item>
+              ),
+              okText: '关闭',
+              onOk() {},
+            });
+          }}
+        >
+          <SearchOutlined />
+        </a>,
+      ],
+    },
+  ];
+
+  const createColumn = (): ProColumns[] =>
+    data.valueType?.type === 'object'
+      ? data.valueType.properties.map(
+          (i: any) =>
+            ({
+              key: i.id,
+              title: i.name,
+              dataIndex: i.id,
+              renderText: (text) => (typeof text === 'object' ? JSON.stringify(text) : text),
+            } as ProColumns),
+        )
+      : [
+          {
+            title: '数据',
+            dataIndex: 'value',
+            ellipsis: true,
+            // render: (text) => text ? JSON.stringify(text) : ''
+          },
+        ];
+
+  return (
+    <div>
+      <SearchComponent<any>
+        field={[...createColumn(), ...columns]}
+        target="events"
+        pattern={'simple'}
+        onSearch={(param) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setSearchParams(param);
+        }}
+        onReset={() => {
+          // 重置分页及搜索参数
+          actionRef.current?.reset?.();
+          setSearchParams({});
+        }}
+      />
+      <ProTable
+        size="small"
+        rowKey="id"
+        actionRef={actionRef}
+        search={false}
+        params={searchParams}
+        toolBarRender={false}
+        request={async (param) => {
+          param.pageIndex = param.current - 1;
+          delete param.current;
+          delete param.total;
+          return service.getEventCount(
+            params.id,
+            data.id!,
+            {
+              ...param,
+            }
+          );
+        }}
+        pagination={{
+          pageSize: 10,
+        }}
+        columns={[...createColumn(), ...columns]}
+      />
+    </div>
+  );
+};
+export default EventLog;

+ 13 - 7
src/pages/device/Instance/Detail/Running/Property/index.tsx

@@ -79,25 +79,31 @@ const Property = (props: Props) => {
       title: '操作',
       key: 'action',
       render: (text: any, record: any) => (
-        <Space size="middle" style={{ color: '#1d39c4' }}>
+        <Space size="middle">
           {(record.expands?.readOnly === false || record.expands?.readOnly === 'false') && (
-            <EditOutlined
+            <a
               onClick={() => {
                 setVisible(true);
               }}
-            />
+            >
+              <EditOutlined />
+            </a>
           )}
-          <SyncOutlined
+          <a
             onClick={() => {
               refreshProperty(record?.id);
             }}
-          />
-          <UnorderedListOutlined
+          >
+            <SyncOutlined />
+          </a>
+          <a
             onClick={() => {
               setCurrentInfo(record);
               setInfoVisible(true);
             }}
-          />
+          >
+            <UnorderedListOutlined />
+          </a>
         </Space>
       ),
     },

+ 7 - 169
src/pages/device/Instance/Detail/Running/index.tsx

@@ -1,185 +1,23 @@
 import { InstanceModel } from '@/pages/device/Instance';
 import { Card, Tabs } from 'antd';
 import type { DeviceMetadata } from '@/pages/device/Product/typings';
-// import { useIntl } from '@@/plugin-locale/localeExports';
-import { useEffect } from 'react';
 import Property from '@/pages/device/Instance/Detail/Running/Property';
-// import Event from '@/pages/device/Instance/Detail/Running/Event';
-// import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
-// import { map } from 'rxjs/operators';
-// import moment from 'moment';
-// import { deviceStatus } from '@/pages/device/Instance/Detail';
-
-// const ColResponsiveProps = {
-//   xs: 24,
-//   sm: 12,
-//   md: 12,
-//   lg: 12,
-//   xl: 6,
-//   style: { marginBottom: 24 },
-// };
+import Event from '@/pages/device/Instance/Detail/Running/Event';
 
 const Running = () => {
-  // const intl = useIntl();
   const metadata = JSON.parse(InstanceModel.detail.metadata as string) as DeviceMetadata;
 
-  // const device = InstanceModel.detail;
-  // const [subscribeTopic] = useSendWebsocketMessage();
-
-  // const addObserver = (item: Record<string, any>) => {
-  //   item.listener = [];
-  //   item.subscribe = (callback: () => void) => {
-  //     item.listener.push(callback);
-  //   };
-  //   item.next = (data: any) => {
-  //     item.listener.forEach((element: any) => {
-  //       element(data);
-  //     });
-  //   };
-  //   return item;
-  // };
-  // metadata.events = metadata.events.map(addObserver);
-  // metadata.properties = metadata.properties.map(addObserver);
-  // const [propertiesList, setPropertiesList] = useState<string[]>(
-  //   metadata.properties.map((item: any) => item.id),
-  // );
-
-  /**
-   * 订阅属性数据
-   */
-  // const subscribeProperty = () => {
-  //   const id = `instance-info-property-${device.id}-${device.productId}-${propertiesList.join(
-  //     '-',
-  //   )}`;
-  //   const topic = `/dashboard/device/${device.productId}/properties/realTime`;
-  //   subscribeTopic!(id, topic, {
-  //     deviceId: device.id,
-  //     properties: propertiesList,
-  //     history: 0,
-  //   })
-  //     ?.pipe(map((res) => res.payload))
-  //     .subscribe((payload: any) => {
-  //       const property = metadata.properties.find(
-  //         (i) => i.id === payload.value.property,
-  //       ) as PropertyMetadata & ObserverMetadata;
-  //       if (property) {
-  //         property.next(payload);
-  //       }
-  //     });
-  // };
-
-  // const getDashboard = () => {
-  //   const params = [
-  //     {
-  //       dashboard: 'device',
-  //       object: device.productId,
-  //       measurement: 'properties',
-  //       dimension: 'history',
-  //       params: {
-  //         deviceId: device.id,
-  //         history: 15,
-  //         properties: propertiesList,
-  //       },
-  //     },
-  //   ];
-
-  //   service.propertyRealTime(params).subscribe({
-  //     next: (data) => {
-  //       const index = metadata.properties.findIndex((i) => i.id === data.property);
-  //       if (index > -1) {
-  //         const property = metadata.properties[index] as PropertyMetadata & ObserverMetadata;
-  //         property.list = data.list as Record<string, unknown>[];
-  //         property.next(data.list);
-  //       }
-  //     },
-  //   });
-  // };
-
-  // /**
-  //  * 订阅事件数据
-  //  */
-  // const subscribeEvent = () => {
-  //   const id = `instance-info-event-${device.id}-${device.productId}`;
-  //   const topic = `/dashboard/device/${device.productId}/events/realTime`;
-  //   subscribeTopic!(id, topic, { deviceId: device.id })
-  //     ?.pipe(map((res) => res.payload))
-  //     .subscribe((payload: any) => {
-  //       const event = metadata.events.find((i) => i.id === payload.value.event) as EventMetadata &
-  //         ObserverMetadata;
-  //       if (event) {
-  //         event.next(payload);
-  //       }
-  //     });
-  // };
-  useEffect(() => {
-    // subscribeProperty();
-    // subscribeEvent();
-    // getDashboard();
-  }, []);
-
-  // const [renderCount, setRenderCount] = useState<number>(15);
-  // window.onscroll = () => {
-  //   const a = document.documentElement.scrollTop;
-  //   const c = document.documentElement.scrollHeight;
-  //   const b = document.body.clientHeight;
-  //   if (a + b >= c - 50) {
-  //     const list: any = [];
-  //     metadata.properties.slice(renderCount, renderCount + 15).map((item) => {
-  //       list.push(item.id);
-  //     });
-  //     setPropertiesList([...list]);
-  //     setRenderCount(renderCount + 15);
-  //   }
-  // };
-
-  // const renderCard = useCallback(() => {
-  //   return [
-  //     ...metadata.properties.map((item) => (
-  //       <Col {...ColResponsiveProps} key={item.id}>
-  //         <Property data={item as Partial<PropertyMetadata> & ObserverMetadata} />
-  //       </Col>
-  //     )),
-  //     ...metadata.events.map((item) => (
-  //       <Col {...ColResponsiveProps} key={item.id}>
-  //         <Event data={item as Partial<EventMetadata> & ObserverMetadata} />
-  //       </Col>
-  //     )),
-  //   ].splice(0, renderCount);
-  // }, [device, renderCount]);
-
   return (
-    // <Row gutter={24}>
-    //   <Col {...ColResponsiveProps}>
-    //     <Card
-    //       title={intl.formatMessage({
-    //         id: 'pages.device.instanceDetail.running.status',
-    //         defaultMessage: '设备状态',
-    //       })}
-    //     >
-    //       <div style={{ height: 60 }}>
-    //         <Row gutter={[16, 16]}>
-    //           <Col span={24}>{deviceStatus.get(InstanceModel.detail.state?.value)}</Col>
-    //           <Col span={24}>
-    //             {device.state?.value === 'online' ? (
-    //               <span>上线时间:{moment(device?.onlineTime).format('YYYY-MM-DD HH:mm:ss')}</span>
-    //             ) : (
-    //               <span>离线时间:{moment(device?.offlineTime).format('YYYY-MM-DD HH:mm:ss')}</span>
-    //             )}
-    //           </Col>
-    //         </Row>
-    //       </div>
-    //     </Card>
-    //   </Col>
-    //   {renderCard()}
-    // </Row>
     <Card>
-      <Tabs defaultActiveKey="1" tabPosition="left">
+      <Tabs defaultActiveKey="1" tabPosition="left" style={{ height: 600 }}>
         <Tabs.TabPane tab="属性" key="1">
           <Property data={metadata?.properties || {}} />
         </Tabs.TabPane>
-        <Tabs.TabPane tab="事件1" key="2">
-          Content of Tab Pane 2
-        </Tabs.TabPane>
+        {metadata.events.map((item) => (
+          <Tabs.TabPane tab={item.name} key={item.id}>
+            <Event data={item} />
+          </Tabs.TabPane>
+        ))}
       </Tabs>
     </Card>
   );

+ 18 - 10
src/pages/device/Instance/Detail/index.tsx

@@ -11,6 +11,7 @@ import Alarm from '@/pages/device/components/Alarm';
 import Info from '@/pages/device/Instance/Detail/Info';
 import Functions from '@/pages/device/Instance/Detail/Functions';
 import Running from '@/pages/device/Instance/Detail/Running';
+import ChildDevice from '@/pages/device/Instance/Detail/ChildDevice'
 import { useIntl } from '@@/plugin-locale/localeExports';
 import Metadata from '../../components/Metadata';
 import type { DeviceMetadata } from '@/pages/device/Product/typings';
@@ -32,16 +33,6 @@ const InstanceDetail = observer(() => {
     });
   };
   const params = useParams<{ id: string }>();
-  useEffect(() => {
-    if (!InstanceModel.current && !params.id) {
-      history.goBack();
-    } else {
-      getDetail(InstanceModel.current?.id || params.id);
-    }
-    return () => {
-      MetadataAction.clean();
-    };
-  }, [params.id]);
 
   const resetMetadata = async () => {
     const resp = await service.deleteMetadata(params.id);
@@ -103,6 +94,12 @@ const InstanceDetail = observer(() => {
       }),
       component: <Log />,
     },
+    // 产品类型为网关的情况下才显示此模块
+    {
+      key: 'child-device',
+      tab: "子设备",
+      component: <ChildDevice />,
+    },
     {
       key: 'alarm',
       tab: intl.formatMessage({
@@ -125,6 +122,17 @@ const InstanceDetail = observer(() => {
     },
   ];
 
+  useEffect(() => {
+    if (!InstanceModel.current && !params.id) {
+      history.goBack();
+    } else {
+      getDetail(InstanceModel.current?.id || params.id);
+    }
+    return () => {
+      MetadataAction.clean();
+    };
+  }, [params.id]);
+
   return (
     <PageContainer
       onBack={history.goBack}

+ 19 - 4
src/pages/device/Instance/service.ts

@@ -58,8 +58,8 @@ class Service extends BaseService<DeviceInstance> {
 
   public getEventCount = (deviceId: string, eventId: string, params: Record<string, unknown>) =>
     request(`/${SystemConst.API_BASE}/device/instance/${deviceId}/event/${eventId}`, {
-      method: 'GET',
-      params,
+      method: 'POST',
+      data: params,
     });
 
   public deleteMetadata = (deviceId: string) =>
@@ -75,14 +75,29 @@ class Service extends BaseService<DeviceInstance> {
 
   public queryLog = (deviceId: string, params: Record<string, unknown>) =>
     request(`/${SystemConst.API_BASE}/device-instance/${deviceId}/logs`, {
-      method: 'GET',
-      params,
+      method: 'POST',
+      data: params,
     });
 
   public getLogType = () =>
     request(`/${SystemConst.API_BASE}/dictionary/device-log-type/items`, {
       method: 'GET',
     });
+  public bindDevice = (deviceId: string, data: string[]) =>
+    request(`/${SystemConst.API_BASE}/device/gateway/${deviceId}/bind`, {
+      method: 'POST',
+      data
+    });
+  public unbindDevice = (deviceId: string, childrenId: string, data: Record<string, unknown>) =>
+    request(`/${SystemConst.API_BASE}/device/gateway/${deviceId}/unbind/${childrenId}`, {
+      method: 'POST',
+      data
+    });
+  public unbindBatchDevice = (deviceId: string, data: string[]) =>
+    request(`/${SystemConst.API_BASE}/device/gateway/${deviceId}/unbind`, {
+      method: 'POST',
+      data
+    });
 }
 
 export default Service;