lind 4 лет назад
Родитель
Сommit
415594362e
47 измененных файлов с 1545 добавлено и 1097 удалено
  1. BIN
      public/images/protocol.png
  2. 27 25
      src/components/BaseCrud/index.tsx
  3. 6 6
      src/components/Metadata/ArrayParam/index.tsx
  4. 13 6
      src/components/PermissionButton/index.tsx
  5. 10 2
      src/components/ProTableCard/CardItems/AccessConfig/index.less
  6. 25 14
      src/components/ProTableCard/CardItems/AccessConfig/index.tsx
  7. 64 0
      src/components/ProTableCard/CardItems/protocol.tsx
  8. 8 8
      src/components/ProTableCard/index.tsx
  9. 2 0
      src/hooks/index.ts
  10. 6 6
      src/hooks/permission/index.ts
  11. 30 0
      src/hooks/route/useHistory.tsx
  12. 22 0
      src/hooks/route/useLocation.tsx
  13. 3 2
      src/locales/zh-CN/pages.ts
  14. 9 9
      src/pages/device/Category/Save/index.tsx
  15. 50 47
      src/pages/device/Instance/Detail/Config/index.tsx
  16. 17 11
      src/pages/device/Instance/Detail/Diagnose/Status/index.tsx
  17. 19 19
      src/pages/device/Instance/Detail/Diagnose/index.tsx
  18. 14 12
      src/pages/device/Instance/Detail/Info/index.tsx
  19. 51 34
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx
  20. 32 19
      src/pages/device/Instance/Detail/index.tsx
  21. 7 9
      src/pages/device/Instance/Save/index.tsx
  22. 24 36
      src/pages/device/Instance/index.tsx
  23. 48 58
      src/pages/device/Product/Detail/Access/index.tsx
  24. 12 9
      src/pages/device/Product/Detail/BaseInfo/index.tsx
  25. 74 75
      src/pages/device/Product/Detail/index.tsx
  26. 38 26
      src/pages/device/Product/index.tsx
  27. 75 64
      src/pages/device/components/Metadata/Base/index.tsx
  28. 21 17
      src/pages/device/components/Metadata/index.tsx
  29. 75 65
      src/pages/link/AccessConfig/Detail/Access/index.tsx
  30. 20 11
      src/pages/link/AccessConfig/Detail/Media/index.tsx
  31. 14 6
      src/pages/link/AccessConfig/Detail/index.tsx
  32. 10 10
      src/pages/link/Certificate/index.tsx
  33. 13 13
      src/pages/link/Gateway/index.tsx
  34. 170 266
      src/pages/link/Protocol/index.tsx
  35. 256 0
      src/pages/link/Protocol/save/index.tsx
  36. 14 11
      src/pages/media/Device/Channel/index.tsx
  37. 119 52
      src/pages/media/Device/Save/ProviderSelect.tsx
  38. 8 8
      src/pages/media/Device/Save/SaveProduct.tsx
  39. 15 10
      src/pages/media/Device/Save/index.tsx
  40. 10 15
      src/pages/media/Device/index.tsx
  41. 12 12
      src/pages/system/Menu/Detail/buttons.tsx
  42. 11 23
      src/pages/system/Menu/Detail/edit.tsx
  43. 15 19
      src/pages/system/Menu/index.tsx
  44. 50 36
      src/pages/system/Permission/index.tsx
  45. 14 14
      src/pages/system/Role/index.tsx
  46. 11 11
      src/pages/system/Tenant/index.tsx
  47. 1 1
      src/utils/menu/router.ts

BIN
public/images/protocol.png


+ 27 - 25
src/components/BaseCrud/index.tsx

@@ -1,22 +1,22 @@
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { Button } from 'antd';
-import type { ActionType, ProColumns, RequestData } from '@jetlinks/pro-table';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import {Button, Tooltip} from 'antd';
+import type {ActionType, ProColumns, RequestData} from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 
-import { PlusOutlined } from '@ant-design/icons';
+import {PlusOutlined} from '@ant-design/icons';
 import type BaseService from '@/utils/BaseService';
 import * as React from 'react';
-import { useRef, useState } from 'react';
+import {useRef, useState} from 'react';
 import Save from '@/components/BaseCrud/save';
-import type { ISchema } from '@formily/json-schema';
-import { CurdModel } from '@/components/BaseCrud/model';
-import type { ISchemaFieldProps } from '@formily/react/lib/types';
-import type { ModalProps } from 'antd/lib/modal/Modal';
-import type { TablePaginationConfig } from 'antd/lib/table/interface';
-import type { Form } from '@formily/core';
+import type {ISchema} from '@formily/json-schema';
+import {CurdModel} from '@/components/BaseCrud/model';
+import type {ISchemaFieldProps} from '@formily/react/lib/types';
+import type {ModalProps} from 'antd/lib/modal/Modal';
+import type {TablePaginationConfig} from 'antd/lib/table/interface';
+import type {Form} from '@formily/core';
 import SearchComponent from '@/components/SearchComponent';
-import type { ProFormInstance } from '@ant-design/pro-form';
-import type { SearchConfig } from '@ant-design/pro-form/lib/components/Submitter';
+import type {ProFormInstance} from '@ant-design/pro-form';
+import type {SearchConfig} from '@ant-design/pro-form/lib/components/Submitter';
 
 export type Option = {
   model: 'edit' | 'preview' | 'add';
@@ -121,18 +121,20 @@ const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
         }
         dateFormatter="string"
         headerTitle={
-          <Button
-            disabled={props.disableAdd}
-            onClick={CurdModel.add}
-            key="button"
-            icon={<PlusOutlined />}
-            type="primary"
-          >
-            {intl.formatMessage({
-              id: 'pages.data.option.add',
-              defaultMessage: '新增',
-            })}
-          </Button>
+          <Tooltip title={props.disableAdd ? '暂无权限,请联系管理员' : ''}>
+            <Button
+              disabled={props.disableAdd}
+              onClick={CurdModel.add}
+              key="button"
+              icon={<PlusOutlined/>}
+              type="primary"
+            >
+              {intl.formatMessage({
+                id: 'pages.data.option.add',
+                defaultMessage: '新增',
+              })}
+            </Button>
+          </Tooltip>
         }
         defaultParams={defaultParams}
         // toolBarRender={() =>

+ 6 - 6
src/components/Metadata/ArrayParam/index.tsx

@@ -1,9 +1,9 @@
-import { createSchemaField } from '@formily/react';
-import { Editable, FormItem, FormLayout, Input, NumberPicker, Select } from '@formily/antd';
-import type { ISchema } from '@formily/json-schema';
+import {createSchemaField} from '@formily/react';
+import {Editable, FormItem, FormLayout, Input, NumberPicker, Select} from '@formily/antd';
+import type {ISchema} from '@formily/json-schema';
 import './index.less';
-import { DataTypeList, DateTypeList, FileTypeList } from '@/pages/device/data';
-import { Store } from 'jetlinks-store';
+import {DataTypeList, DateTypeList, FileTypeList} from '@/pages/device/data';
+import {Store} from 'jetlinks-store';
 import JsonParam from '@/components/Metadata/JsonParam';
 import EnumParam from '@/components/Metadata/EnumParam';
 import BooleanEnum from '@/components/Metadata/BooleanParam';
@@ -178,7 +178,7 @@ const ArrayParam = () => {
           },
 
           description: {
-            title: '描述',
+            title: '说明',
             'x-decorator': 'FormItem',
             'x-component': 'Input.TextArea',
           },

+ 13 - 6
src/components/PermissionButton/index.tsx

@@ -1,13 +1,14 @@
-import { Button, Tooltip, Popconfirm } from 'antd';
-import type { TooltipProps, PopconfirmProps, ButtonProps } from 'antd';
+import type {ButtonProps, PopconfirmProps, TooltipProps} from 'antd';
+import {Button, Popconfirm, Tooltip} from 'antd';
 import usePermissions from '@/hooks/permission';
-import { useCallback } from 'react';
-import { useIntl } from '@@/plugin-locale/localeExports';
+import {useCallback} from 'react';
+import {useIntl} from '@@/plugin-locale/localeExports';
 
 interface PermissionButtonProps extends ButtonProps {
   tooltip?: TooltipProps;
   popConfirm?: PopconfirmProps;
   isPermission?: boolean;
+  noButton?: boolean;
 }
 
 /**
@@ -27,7 +28,13 @@ const PermissionButton = (props: PermissionButtonProps) => {
 
   const intl = useIntl();
 
-  const defaultButton = <Button {...buttonProps} disabled={_isPermission} />;
+  const isButton = 'noButton' in props && props.noButton;
+
+  const defaultButton = isButton ? (
+    props.children
+  ) : (
+    <Button {...buttonProps} disabled={_isPermission}/>
+  );
 
   const isTooltip = tooltip ? <Tooltip {...tooltip}>{defaultButton}</Tooltip> : null;
 
@@ -38,7 +45,7 @@ const PermissionButton = (props: PermissionButtonProps) => {
         defaultMessage: '没有权限',
       })}
     >
-      {defaultButton}
+      {<Button {...buttonProps} disabled={_isPermission}/>}
     </Tooltip>
   );
 

+ 10 - 2
src/components/ProTableCard/CardItems/AccessConfig/index.less

@@ -57,7 +57,7 @@
       .container {
         display: flex;
         width: 100%;
-        min-height: 50px;
+        min-height: 60px;
         margin-top: 10px;
 
         .server,
@@ -65,9 +65,15 @@
           width: calc(50% - 20px);
           margin-right: 10px;
 
+          .serverItem {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+          }
+
           .subTitle {
             width: 100%;
-            margin-bottom: 5px;
             overflow: hidden;
             color: rgba(0, 0, 0, 0.75);
             font-size: 12px;
@@ -79,6 +85,8 @@
             width: 100%;
             height: 20px;
             overflow: hidden;
+            color: #666;
+            font-size: 12px;
             white-space: nowrap;
             text-overflow: ellipsis;
           }

+ 25 - 14
src/components/ProTableCard/CardItems/AccessConfig/index.tsx

@@ -1,9 +1,9 @@
 import React from 'react';
-import { StatusColorEnum } from '@/components/BadgeStatus';
-import { TableCard } from '@/components';
+import {StatusColorEnum} from '@/components/BadgeStatus';
+import {TableCard} from '@/components';
 import '@/style/common.less';
-import { Badge, Tooltip } from 'antd';
-import type { AccessItem } from '@/pages/link/AccessConfig/typings';
+import {Badge, Tooltip} from 'antd';
+import type {AccessItem} from '@/pages/link/AccessConfig/typings';
 import './index.less';
 import classNames from 'classnames';
 
@@ -38,23 +38,34 @@ export default (props: AccessConfigCardProps) => {
         </div>
         <div className="card">
           <div className="header">
-            <Tooltip title={props.name}>
-              <div className="title ellipsis">{props.name || '--'}</div>
-            </Tooltip>
-            <div className="desc">{props.description || '--'}</div>
+            <div className="title ellipsis">
+              <Tooltip title={props.name}>{props.name || '--'}</Tooltip>
+            </div>
+            <div className="desc">
+              <Tooltip title={props.description}>{props.description || '--'}</Tooltip>
+            </div>
           </div>
           <div className="container">
             <div className="server">
               <div className="subTitle">{props?.channelInfo?.name || '--'}</div>
-              {props.channelInfo?.addresses.slice(0, 2).map((i: any, index: number) => (
-                <div className="subItem" key={i.address + `_address${index}`}>
-                  <Badge color={i.health === -1 ? 'red' : 'green'} text={i.address} />
-                </div>
-              ))}
+              <div className="serverItem">
+                {props.channelInfo?.addresses.slice(0, 2).map((i: any, index: number) => (
+                  <div className="subItem" key={i.address + `_address${index}`}>
+                    <Tooltip title={i.address}>
+                      <Badge color={i.health === -1 ? 'red' : 'green'}/>
+                      {i.address}
+                    </Tooltip>
+                  </div>
+                ))}
+              </div>
             </div>
             <div className="procotol">
               <div className="subTitle">{props?.protocolDetail?.name || '--'}</div>
-              <div className="desc">{props.protocolDetail?.description || '--'}</div>
+              <div className="desc">
+                <Tooltip title={props.protocolDetail?.description}>
+                  {props.protocolDetail?.description || '--'}
+                </Tooltip>
+              </div>
             </div>
           </div>
         </div>

+ 64 - 0
src/components/ProTableCard/CardItems/protocol.tsx

@@ -0,0 +1,64 @@
+import React from 'react';
+import type {ProtocolItem} from '@/pages/link/Protocol/typings';
+import {StatusColorEnum} from '@/components/BadgeStatus';
+import {TableCard} from '@/components';
+import '@/style/common.less';
+import '../index.less';
+import {Col, Row, Tooltip} from 'antd';
+
+export interface ProcotolCardProps extends ProtocolItem {
+  detail?: React.ReactNode;
+  actions?: React.ReactNode[];
+  avatarSize?: number;
+}
+
+const defaultImage = require('/public/images/protocol.png');
+
+export default (props: ProcotolCardProps) => {
+  return (
+    <TableCard
+      showMask={false}
+      actions={props.actions}
+      status={props.state === 1 ? 'enabled' : 'disabled'}
+      statusText={props.state === 1 ? '已发布' : '未发布'}
+      statusNames={{
+        enabled: StatusColorEnum.processing,
+        disabled: StatusColorEnum.error,
+      }}
+    >
+      <div className={'pro-table-card-item'}>
+        <div className={'card-item-avatar'}>
+          <img width={88} height={88} src={defaultImage} alt={''}/>
+        </div>
+        <div className={'card-item-body'}>
+          <div className={'card-item-header'}>
+            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+          </div>
+          <Row gutter={24}>
+            <Col span={12}>
+              <div>
+                <div style={{color: 'rgba(0, 0, 0, 0.75)', fontSize: 12}}>ID</div>
+                <div
+                  style={{
+                    width: '100%',
+                    overflow: 'hidden',
+                    whiteSpace: 'nowrap',
+                    textOverflow: 'ellipsis',
+                  }}
+                >
+                  <Tooltip title={props.id}>{props.id}</Tooltip>
+                </div>
+              </div>
+            </Col>
+            <Col span={12}>
+              <div>
+                <div style={{color: 'rgba(0, 0, 0, 0.75)', fontSize: 12}}>类型</div>
+                <div>{props.type}</div>
+              </div>
+            </Col>
+          </Row>
+        </div>
+      </div>
+    </TableCard>
+  );
+};

+ 8 - 8
src/components/ProTableCard/index.tsx

@@ -1,10 +1,10 @@
-import type { ProTableProps } from '@jetlinks/pro-table';
+import type {ProTableProps} from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import type { ParamsType } from '@ant-design/pro-provider';
-import React, { useEffect, useState } from 'react';
-import { isFunction } from 'lodash';
-import { Empty, Pagination, Space } from 'antd';
-import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
+import type {ParamsType} from '@ant-design/pro-provider';
+import React, {useEffect, useState} from 'react';
+import {isFunction} from 'lodash';
+import {Empty, Pagination, Space} from 'antd';
+import {AppstoreOutlined, BarsOutlined} from '@ant-design/icons';
 import classNames from 'classnames';
 import './index.less';
 
@@ -63,9 +63,9 @@ const ProTableCard = <
   };
 
   const windowChange = () => {
-    if (window.innerWidth <= 1366) {
+    if (window.innerWidth <= 1440) {
       setColumn(props.gridColumn && props.gridColumn < 2 ? props.gridColumn : 2);
-    } else if (window.innerWidth > 1366 && window.innerWidth <= 1600) {
+    } else if (window.innerWidth > 1440 && window.innerWidth <= 1600) {
       setColumn(props.gridColumn && props.gridColumn < 3 ? props.gridColumn : 3);
     } else if (window.innerWidth > 1600) {
       setColumn(props.gridColumn && props.gridColumn < 4 ? props.gridColumn : 4);

+ 2 - 0
src/hooks/index.ts

@@ -0,0 +1,2 @@
+export {default as useHistory} from './route/useHistory';
+export {default as useLocation} from './route/useLocation';

+ 6 - 6
src/hooks/permission/index.ts

@@ -1,10 +1,10 @@
-import { useEffect, useState } from 'react';
-import { BUTTON_PERMISSION_ENUM } from '@/utils/menu/router';
-import type { MENUS_CODE_TYPE, BUTTON_PERMISSION } from '@/utils/menu/router';
-import { MENUS_BUTTONS_CACHE } from '@/utils/menu';
+import {useEffect, useState} from 'react';
+import type {BUTTON_PERMISSION, MENUS_CODE_TYPE} from '@/utils/menu/router';
+import {BUTTON_PERMISSION_ENUM} from '@/utils/menu/router';
+import {MENUS_BUTTONS_CACHE} from '@/utils/menu';
 
-type permissionKeyType = keyof typeof BUTTON_PERMISSION_ENUM;
-type permissionType = Record<permissionKeyType, boolean>;
+export type permissionKeyType = keyof typeof BUTTON_PERMISSION_ENUM;
+export type permissionType = Record<permissionKeyType, boolean>;
 
 const usePermissions = (
   code: MENUS_CODE_TYPE,

+ 30 - 0
src/hooks/route/useHistory.tsx

@@ -0,0 +1,30 @@
+import {useHistory} from 'umi';
+import {useEffect, useState} from 'react';
+import type {LocationDescriptor, LocationState, Path} from 'history';
+import {model} from '@formily/reactive';
+
+export const historyStateModel = model<{ state: any }>({state: {}});
+
+const useHistories = () => {
+  const umiHistory = useHistory();
+
+  const [history, setHistory] = useState<any>();
+
+  const push = (location: Path | LocationDescriptor<LocationState>, state?: LocationState) => {
+    if (state) {
+      historyStateModel.state = state;
+    }
+    umiHistory.push(location, state);
+  };
+
+  useEffect(() => {
+    setHistory({
+      ...umiHistory,
+      push,
+    });
+  }, []);
+
+  return history;
+};
+
+export default useHistories;

+ 22 - 0
src/hooks/route/useLocation.tsx

@@ -0,0 +1,22 @@
+import {useLocation} from 'umi';
+import {useEffect, useState} from 'react';
+import {historyStateModel} from '@/hooks/route/useHistory';
+
+const useLocations = () => {
+  const umiLocation = useLocation();
+  const [location, setLocation] = useState<any>({});
+  useEffect(() => {
+    setLocation({
+      ...umiLocation,
+      state: historyStateModel.state,
+    });
+
+    return () => {
+      historyStateModel.state = undefined;
+    };
+  }, [umiLocation]);
+
+  return location;
+};
+
+export default useLocations;

+ 3 - 2
src/locales/zh-CN/pages.ts

@@ -42,7 +42,7 @@ export default {
   'pages.table.name': '名称',
   'pages.table.deviceName': '设备名称',
   'pages.table.productName': '产品名称',
-  'pages.table.describe': '描述',
+  'pages.table.describe': '说明',
   'pages.table.description': '说明',
   'pages.table.provider': '服务商',
   'pages.table.type': '类型',
@@ -149,6 +149,7 @@ export default {
   'pages.system.menu.buttons': '按钮管理',
   'pages.system.menu.root': '菜单权限',
   'page.system.menu.sort': '排序',
+  'page.system.menu.describe': '说明',
   // 系统设置-第三方平台
   'pages.system.openApi': '第三方平台',
   'pages.system.openApi.username': '用户名',
@@ -223,7 +224,7 @@ export default {
   'pages.device.productDetail.metadata.fileType': '文件类型',
   'pages.device.productDetail.metadata.source': '来源',
   'pages.device.productDetail.metadata.otherConfiguration': '其他配置',
-  'pages.device.productDetail.metadata.describe': '描述',
+  'pages.device.productDetail.metadata.describe': '说明',
   'pages.device.productDetail.metadata.inputParameter': '输入参数',
   'pages.device.productDetail.metadata.outputParameters': '输出参数',
   'pages.device.productDetail.alarm': '告警设置',

+ 9 - 9
src/pages/device/Category/Save/index.tsx

@@ -13,16 +13,16 @@ import {
   Upload,
 } from '@formily/antd';
 import React from 'react';
-import { createForm } from '@formily/core';
-import { createSchemaField } from '@formily/react';
+import {createForm} from '@formily/core';
+import {createSchemaField} from '@formily/react';
 import FUpload from '@/components/Upload';
 import * as ICONS from '@ant-design/icons';
-import { message, Modal } from 'antd';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import type { ISchema } from '@formily/json-schema';
-import type { CategoryItem } from '@/pages/visualization/Category/typings';
-import { service, state } from '@/pages/device/Category';
-import type { Response } from '@/utils/typings';
+import {message, Modal} from 'antd';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import type {ISchema} from '@formily/json-schema';
+import type {CategoryItem} from '@/pages/visualization/Category/typings';
+import {service, state} from '@/pages/device/Category';
+import type {Response} from '@/utils/typings';
 
 interface Props {
   visible: boolean;
@@ -135,7 +135,7 @@ const Save = (props: Props) => {
         'x-component': 'Input.TextArea',
         'x-component-props': {
           rows: 3,
-          placeholder: '请输入描述',
+          placeholder: '请输入说明',
         },
         name: 'description',
       },

+ 50 - 47
src/pages/device/Instance/Detail/Config/index.tsx

@@ -1,15 +1,11 @@
-import { Button, Descriptions, message, Popconfirm, Space, Tooltip } from 'antd';
-import { InstanceModel, service } from '@/pages/device/Instance';
-import { useEffect, useState } from 'react';
-import type { ConfigMetadata } from '@/pages/device/Product/typings';
-import { history, useParams } from 'umi';
-import {
-  CheckOutlined,
-  EditOutlined,
-  QuestionCircleOutlined,
-  UndoOutlined,
-} from '@ant-design/icons';
+import {Descriptions, message, Space, Tooltip} from 'antd';
+import {InstanceModel, service} from '@/pages/device/Instance';
+import {useEffect, useState} from 'react';
+import type {ConfigMetadata} from '@/pages/device/Product/typings';
+import {history, useParams} from 'umi';
+import {CheckOutlined, EditOutlined, QuestionCircleOutlined, UndoOutlined,} from '@ant-design/icons';
 import Edit from './Edit';
+import {PermissionButton} from '@/components';
 
 const Config = () => {
   const params = useParams<{ id: string }>();
@@ -26,6 +22,7 @@ const Config = () => {
 
   const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
   const [visible, setVisible] = useState<boolean>(false);
+  const {permission} = PermissionButton.usePermission('device/Instance');
 
   const id = InstanceModel.detail?.id || params?.id;
 
@@ -38,6 +35,7 @@ const Config = () => {
   };
 
   useEffect(() => {
+    console.log(id);
     if (id) {
       service.getConfigMetadata(id).then((config) => {
         setMetadata(config?.result);
@@ -78,61 +76,66 @@ const Config = () => {
     }
   };
 
-  return (
-    <div style={{ width: '100%', marginTop: '20px' }} className="config">
-      <div style={{ display: 'flex', marginBottom: 20 }}>
-        <div style={{ fontSize: 16, fontWeight: 700 }}>配置</div>
+  return metadata.length > 0 ? (
+    <div style={{width: '100%', marginTop: '20px'}} className="config">
+      <div style={{display: 'flex', marginBottom: 20}}>
+        <div style={{fontSize: 16, fontWeight: 700}}>配置</div>
         <Space>
-          <Button
+          <PermissionButton
+            isPermission={permission.update}
             type="link"
             onClick={async () => {
               setVisible(true);
             }}
           >
-            <EditOutlined />
+            <EditOutlined/>
             编辑
-          </Button>
+          </PermissionButton>
           {InstanceModel.detail.state?.value !== 'notActive' && (
-            <Popconfirm
-              title="确认重新应用该配置?"
-              onConfirm={async () => {
-                const resp = await service.deployDevice(id || '');
-                if (resp.status === 200) {
-                  message.success('操作成功');
-                  getDetail();
-                }
+            <PermissionButton
+              popConfirm={{
+                title: '确认重新应用该配置?',
+                onConfirm: async () => {
+                  const resp = await service.deployDevice(id || '');
+                  if (resp.status === 200) {
+                    message.success('操作成功');
+                    getDetail();
+                  }
+                },
               }}
+              isPermission={permission.update}
+              type="link"
             >
-              <Button type="link">
-                <CheckOutlined />
-                应用配置
-              </Button>
+              <CheckOutlined/>
+              应用配置
               <Tooltip title="修改配置后需重新应用后才能生效。">
-                <QuestionCircleOutlined />
+                <QuestionCircleOutlined/>
               </Tooltip>
-            </Popconfirm>
+            </PermissionButton>
           )}
           {InstanceModel.detail?.aloneConfiguration && (
-            <Popconfirm
-              title="确认恢复默认配置?"
-              onConfirm={async () => {
-                const resp = await service.configurationReset(id || '');
-                if (resp.status === 200) {
-                  message.success('恢复默认配置成功');
-                  getDetail();
-                }
+            <PermissionButton
+              popConfirm={{
+                title: '确认恢复默认配置?',
+                onConfirm: async () => {
+                  const resp = await service.configurationReset(id || '');
+                  if (resp.status === 200) {
+                    message.success('恢复默认配置成功');
+                    getDetail();
+                  }
+                },
               }}
+              type="link"
+              isPermission={permission.update}
             >
-              <Button type="link">
-                <UndoOutlined />
-                恢复默认
-              </Button>
+              <UndoOutlined/>
+              恢复默认
               <Tooltip
                 title={`该设备单独编辑过配置信息,点击此将恢复成默认的配置信息,请谨慎操作。`}
               >
-                <QuestionCircleOutlined />
+                <QuestionCircleOutlined/>
               </Tooltip>
-            </Popconfirm>
+            </PermissionButton>
           )}
         </Space>
       </div>
@@ -172,7 +175,7 @@ const Config = () => {
         />
       )}
     </div>
-  );
+  ) : null;
 };
 
 export default Config;

+ 17 - 11
src/pages/device/Instance/Detail/Diagnose/Status/index.tsx

@@ -1,13 +1,13 @@
 import TitleComponent from '@/components/TitleComponent';
-import { Badge, Button, Col, message, Popconfirm, Row } from 'antd';
-import { useEffect, useState } from 'react';
+import {Badge, Button, Col, message, Popconfirm, Row} from 'antd';
+import {useEffect, useState} from 'react';
 import styles from './index.less';
-import { InstanceModel, service } from '@/pages/device/Instance';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import type { ProductItem } from '@/pages/device/Product/typings';
-import { Store } from 'jetlinks-store';
-import { observer } from '@formily/reactive-react';
-import { DiagnoseStatusModel } from './model';
+import {InstanceModel, service} from '@/pages/device/Instance';
+import {getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import type {ProductItem} from '@/pages/device/Product/typings';
+import {Store} from 'jetlinks-store';
+import {observer} from '@formily/reactive-react';
+import {DiagnoseStatusModel} from './model';
 
 interface Props {
   onChange: (type: string) => void;
@@ -94,6 +94,7 @@ const Status = observer((props: Props) => {
         datalist.push(network);
       }
       setList([...datalist]);
+      return datalist;
     }
   };
 
@@ -460,7 +461,7 @@ const Status = observer((props: Props) => {
     if (deviceConfig?.result?.channelId && deviceConfig?.channel === 'network') {
       network = await service.queryNetworkState(deviceConfig?.channelId);
     }
-    initList(proItem.result, configuration.result, deviceConfig.result);
+    const list1 = initList(proItem.result, configuration.result, deviceConfig.result);
 
     diagnoseProduct(proItem.result)
       .then(() => diagnoseConfig(proItem.result))
@@ -476,7 +477,10 @@ const Status = observer((props: Props) => {
           }
         });
         if (a) {
-          Store.set('diagnose-status', DiagnoseStatusModel.status);
+          Store.set('diagnose-status', {
+            list: list1,
+            status: DiagnoseStatusModel.status,
+          });
           props.onChange('success');
         } else {
           props.onChange('error');
@@ -489,7 +493,9 @@ const Status = observer((props: Props) => {
       handleSearch();
     } else {
       const dt = Store.get('diagnose-status');
-      DiagnoseStatusModel.status = dt;
+      DiagnoseStatusModel.status = dt?.status;
+      console.log(dt.status);
+      setList(dt?.list || []);
       props.onChange('success');
     }
   }, []);

+ 19 - 19
src/pages/device/Instance/Detail/Diagnose/index.tsx

@@ -1,12 +1,11 @@
-import { Badge, Card, Col, Row } from 'antd';
-import type { ReactNode } from 'react';
-import { useEffect } from 'react';
-import { useState } from 'react';
+import {Badge, Card, Col, Row} from 'antd';
+import type {ReactNode} from 'react';
+import {useEffect, useState} from 'react';
 import Message from './Message';
 import Status from './Status';
 import './index.less';
 import classNames from 'classnames';
-import { Store } from 'jetlinks-store';
+import {Store} from 'jetlinks-store';
 
 interface ListProps {
   key: string;
@@ -29,14 +28,14 @@ statusColor.set('s-success-active', '#24B276');
 statusColor.set('s-success', '#24B276');
 statusColor.set('success', '#24B276');
 statusColor.set('waiting', '#FF9000');
-statusColor.set('diaabled', 'rgba(0, 0, 0, .8)');
+statusColor.set('disabled', 'rgba(0, 0, 0, .8)');
 
 const statusText = new Map();
 statusText.set('s-error', '连接失败');
 statusText.set('s-success-active', '连接成功');
 statusText.set('s-success', '连接成功');
 statusText.set('waiting', '诊断中');
-statusText.set('diaabled', '诊断中');
+statusText.set('disabled', '诊断中');
 
 const Diagnose = () => {
   const [current, setCurrent] = useState<string>('status');
@@ -69,26 +68,27 @@ const Diagnose = () => {
       component: (
         <div
           style={
-            message !== 'diaabled'
+            message !== 'disabled'
               ? {
                   backgroundImage: `url(${bImageMap.get(message)})`,
                   backgroundSize: '100% 100%',
                 }
               : {
-                  backgroundColor: 'rgba(0, 0, 0, .08)',
-                  borderLeft: '2px solid rgba(0, 0, 0, .8)',
-                }
+                backgroundColor: 'rgba(0, 0, 0, .08)',
+                borderLeft: '2px solid rgba(0, 0, 0, .8)',
+                cursor: 'not-allowed',
+              }
           }
           className="item-box"
         >
           <div className="item-title">消息通信</div>
           <div
-            className={classNames('item-context', message !== 'diaabled' ? 'item-message' : '')}
-            style={{ fontWeight: 400 }}
+            className={classNames('item-context', message !== 'disabled' ? 'item-message' : '')}
+            style={{fontWeight: 400}}
           >
-            {message === 'diaabled' ? (
-              <span style={{ color: statusColor.get(message) }}>
-                <Badge color={statusColor.get(message)} /> 连接中
+            {message === 'disabled' ? (
+              <span style={{color: statusColor.get(message)}}>
+                <Badge color={statusColor.get(message)}/> 连接中
               </span>
             ) : (
               <>
@@ -159,13 +159,13 @@ const Diagnose = () => {
             onChange={(type: string) => {
               if (type === 'success') {
                 setStatus('s-success-active');
-                setMessage('diaabled');
+                setMessage('waiting');
               } else if (type === 'error') {
                 setStatus('s-error');
-                setMessage('diaabled');
+                setMessage('disabled');
               } else if (type === 'loading') {
                 setStatus('waiting');
-                setMessage('diaabled');
+                setMessage('disabled');
               }
             }}
           />

+ 14 - 12
src/pages/device/Instance/Detail/Info/index.tsx

@@ -1,18 +1,20 @@
-import { Button, Card, Descriptions } from 'antd';
-import { InstanceModel } from '@/pages/device/Instance';
+import {Card, Descriptions} from 'antd';
+import {InstanceModel} from '@/pages/device/Instance';
 import moment from 'moment';
-import { observer } from '@formily/react';
-import { useIntl } from '@@/plugin-locale/localeExports';
+import {observer} from '@formily/react';
+import {useIntl} from '@@/plugin-locale/localeExports';
 import Config from '@/pages/device/Instance/Detail/Config';
 import Save from '../../Save';
-import { useState } from 'react';
-import type { DeviceInstance } from '../../typings';
-import { EditOutlined } from '@ant-design/icons';
+import {useState} from 'react';
+import type {DeviceInstance} from '../../typings';
+import {EditOutlined} from '@ant-design/icons';
 import Tags from '@/pages/device/Instance/Detail/Tags';
+import {PermissionButton} from '@/components';
 
 const Info = observer(() => {
   const intl = useIntl();
   const [visible, setVisible] = useState<boolean>(false);
+  const {permission} = PermissionButton.usePermission('device/Instance');
 
   return (
     <>
@@ -23,16 +25,17 @@ const Info = observer(() => {
           bordered
           title={[
             <span key={1}>设备信息</span>,
-            <Button
+            <PermissionButton
+              isPermission={permission.update}
               key={2}
               type={'link'}
               onClick={() => {
                 setVisible(true);
               }}
             >
-              <EditOutlined />
+              <EditOutlined/>
               编辑
-            </Button>,
+            </PermissionButton>,
           ]}
         >
           <Descriptions.Item
@@ -110,8 +113,7 @@ const Info = observer(() => {
             {InstanceModel.detail?.description}
           </Descriptions.Item>
         </Descriptions>
-        {InstanceModel.detail?.configuration &&
-          Object.keys(InstanceModel.detail?.configuration).length > 0 && <Config />}
+        <Config/>
         {InstanceModel.detail?.tags && InstanceModel.detail?.tags.length > 0 && <Tags />}
       </Card>
       <Save

+ 51 - 34
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -1,11 +1,11 @@
-import React, { useContext, useEffect, useState } from 'react';
-import { Form, Input, message, Pagination, Select, Table } from 'antd';
-import { service } from '@/pages/device/Instance';
+import React, {useContext, useEffect, useState} from 'react';
+import {Form, Input, message, Pagination, Select, Table} from 'antd';
+import {service} from '@/pages/device/Instance';
 import _ from 'lodash';
 
 const EditableContext: any = React.createContext(null);
 
-const EditableRow = ({ ...props }) => {
+const EditableRow = ({...props}) => {
   const [form] = Form.useForm();
 
   return (
@@ -42,11 +42,7 @@ const EditableCell = ({
   const save = async () => {
     try {
       const values = await form.validateFields();
-      if (values?.metadataId) {
-        handleSave({ ...record, ...values });
-      } else {
-        console.log(values);
-      }
+      handleSave({...record, metadataId: values?.metadataId});
     } catch (errInfo) {
       console.log('Save failed:', errInfo);
     }
@@ -66,12 +62,12 @@ const EditableCell = ({
         <Select
           onChange={save}
           showSearch
-          allowClear
           optionFilterProp="children"
           filterOption={(input: string, option: any) =>
             (option?.children || '').toLowerCase()?.indexOf(input.toLowerCase()) >= 0
           }
         >
+          <Select.Option value={'use-origin-data'}>使用原始属性</Select.Option>
           {list.map((item: any) => (
             <Select.Option key={item?.id} value={item?.id}>
               {item?.id}
@@ -89,22 +85,50 @@ interface Props {
   type: 'device' | 'product';
 }
 
+const statusMap = new Map();
+statusMap.set('write', '写');
+statusMap.set('read', '读');
+statusMap.set('report', '上报');
+
 const EditableTable = (props: Props) => {
   const baseColumns = [
     {
-      title: '物模型中属性名',
+      title: '建模属性',
       dataIndex: 'name',
+      render: (text: any, record: any) => <span>{`${record.name}(${record.id})`}</span>,
     },
     {
-      title: '物模型中属性标识',
-      dataIndex: 'id',
-    },
-    {
-      title: '协议中属性标识',
+      title: '映射属性',
       dataIndex: 'metadataId',
       width: '30%',
+      render: (text: any, record: any) => (
+        <span>{`${record.metadataId}(${record.metadataName})`}</span>
+      ),
       editable: true,
     },
+    {
+      title: '属性类型',
+      dataIndex: 'valueType',
+      render: (text: any, record: any) => <span>{record.valueType?.type}</span>,
+    },
+    {
+      title: '读写类型',
+      dataIndex: 'expands',
+      render: (text: any, record: any) => (
+        <span>
+          {(record.expands?.type || [])
+            .map((i: string) => {
+              return statusMap.get(i);
+            })
+            .join(',')}
+        </span>
+      ),
+    },
+    {
+      title: '映射状态',
+      dataIndex: 'customMapping',
+      render: (text: any) => <span>{String(text)}</span>,
+    },
   ];
   const metadata = JSON.parse(props?.data?.metadata || '{}');
   const [properties, setProperties] = useState<any[]>(metadata?.properties || []);
@@ -136,7 +160,7 @@ const EditableTable = (props: Props) => {
       const data = resp.result;
       const obj: any = {};
       data.map((i: any) => {
-        obj[i?.originalId] = i?.metadataId || '';
+        obj[i?.originalId] = i;
       });
       if (protocolMetadata.length > 0) {
         setPmList(protocolMetadata.filter((i) => !_.map(data, 'metadataId').includes(i.id)));
@@ -147,10 +171,11 @@ const EditableTable = (props: Props) => {
         (item: any) => {
           return {
             ...item,
-            metadataId: obj[item.id] || '',
+            ...obj[item.id],
           };
         },
       );
+      console.log(list);
       setProperties([...list]);
       setDataSource({
         data: list.slice(
@@ -184,19 +209,19 @@ const EditableTable = (props: Props) => {
     const newData = [...dataSource.data];
     const index = newData.findIndex((item) => row.id === item.id);
     const item = newData[index];
-    newData.splice(index, 1, { ...item, ...row });
-    setDataSource({
-      ...dataSource,
-      data: [...newData],
-    });
+    // newData.splice(index, 1, { ...item, ...row });
+    // setDataSource({
+    //     ...dataSource,
+    //     data: [...newData],
+    // });
     if (item?.metadataId !== row?.metadataId) {
       const resp = await service[
         props.type === 'device' ? 'saveDeviceMetadata' : 'saveProductMetadata'
-      ](props.data?.id, [
+        ](props.data?.id, [
         {
           metadataType: 'property',
-          metadataId: row.metadataId,
-          originalId: row.id,
+          metadataId: row.metadataId === 'use-origin-data' ? row.metadataId : row.id,
+          originalId: row.metadataId === 'use-origin-data' ? row.id : '',
           others: {},
         },
       ]);
@@ -268,14 +293,6 @@ const EditableTable = (props: Props) => {
             });
           }}
         />
-        {/* <div style={{ color: 'rgba(0, 0, 0, .65)' }}>
-                    <QuestionCircleOutlined style={{ margin: 5 }} />
-                    {
-                        props?.data?.independentMetadata ?
-                            '该设备已脱离产品物模型映射,修改产品物模型映射对该设备物模型映射无影响' :
-                            '设备会默认继承产品的物模型映射,修改设备物模型映射后将脱离产品物模型映射'
-                    }
-                </div> */}
       </div>
       <Table
         components={components}

+ 32 - 19
src/pages/device/Instance/Detail/index.tsx

@@ -1,10 +1,10 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import { InstanceModel, service } from '@/pages/device/Instance';
-import { history, useParams } from 'umi';
-import { Badge, Button, Card, Descriptions, Divider, message, Popconfirm, Tooltip } from 'antd';
-import type { ReactNode } from 'react';
-import { useEffect, useState } from 'react';
-import { observer } from '@formily/react';
+import {PageContainer} from '@ant-design/pro-layout';
+import {InstanceModel, service} from '@/pages/device/Instance';
+import {history, useParams} from 'umi';
+import {Badge, Card, Descriptions, Divider, message} from 'antd';
+import type {ReactNode} from 'react';
+import {useEffect, useState} from 'react';
+import {observer} from '@formily/react';
 import Log from '@/pages/device/Instance/Detail/Log';
 // import Alarm from '@/pages/device/components/Alarm';
 import Info from '@/pages/device/Instance/Detail/Info';
@@ -13,14 +13,15 @@ import Running from '@/pages/device/Instance/Detail/Running';
 import ChildDevice from '@/pages/device/Instance/Detail/ChildDevice';
 import Diagnose from '@/pages/device/Instance/Detail/Diagnose';
 import MetadataMap from '@/pages/device/Instance/Detail/MetadataMap';
-import { useIntl } from '@@/plugin-locale/localeExports';
+import {useIntl} from '@@/plugin-locale/localeExports';
 import Metadata from '../../components/Metadata';
-import type { DeviceMetadata } from '@/pages/device/Product/typings';
+import type {DeviceMetadata} from '@/pages/device/Product/typings';
 import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
-import { Store } from 'jetlinks-store';
+import {Store} from 'jetlinks-store';
 import SystemConst from '@/utils/const';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import {getMenuPathByCode, getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
+import {PermissionButton} from '@/components';
 
 export const deviceStatus = new Map();
 deviceStatus.set('online', <Badge status="success" text={'在线'} />);
@@ -31,6 +32,7 @@ const InstanceDetail = observer(() => {
   const intl = useIntl();
   const [tab, setTab] = useState<string>('detail');
   const params = useParams<{ id: string }>();
+  const {permission} = PermissionButton.usePermission('device/Instance');
 
   const resetMetadata = async () => {
     const resp = await service.deleteMetadata(params.id);
@@ -70,11 +72,19 @@ const InstanceDetail = observer(() => {
           <Metadata
             type="device"
             tabAction={
-              <Popconfirm title="确认重置?" onConfirm={resetMetadata}>
-                <Tooltip title="重置后将使用产品的物模型配置">
-                  <Button>重置操作</Button>
-                </Tooltip>
-              </Popconfirm>
+              <PermissionButton
+                isPermission={permission.update}
+                popConfirm={{
+                  title: '确认重置?',
+                  onConfirm: resetMetadata,
+                }}
+                tooltip={{
+                  title: '重置后将使用产品的物模型配置',
+                }}
+                key={'reload'}
+              >
+                重置操作
+              </PermissionButton>
             }
           />
         </Card>
@@ -197,19 +207,22 @@ const InstanceDetail = observer(() => {
         <Descriptions size="small" column={4}>
           <Descriptions.Item label={'ID'}>{InstanceModel.detail?.id}</Descriptions.Item>
           <Descriptions.Item label={'所属产品'}>
-            <Button
+            <PermissionButton
               type={'link'}
               size={'small'}
+              isPermission={!!getMenuPathByCode(MENUS_CODE['device/Product'])}
               onClick={() => {
                 const url = getMenuPathByParams(
                   MENUS_CODE['device/Product/Detail'],
                   InstanceModel.detail?.productId,
                 );
-                history.replace(url);
+                if (url) {
+                  history.replace(url);
+                }
               }}
             >
               {InstanceModel.detail?.productName}
-            </Button>
+            </PermissionButton>
           </Descriptions.Item>
         </Descriptions>
       }

+ 7 - 9
src/pages/device/Instance/Save/index.tsx

@@ -1,10 +1,10 @@
-import { Col, Form, Input, message, Row, Select } from 'antd';
-import { service } from '@/pages/device/Instance';
-import type { DeviceInstance } from '../typings';
-import { useEffect, useState } from 'react';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { Modal, UploadImage } from '@/components';
-import { debounce } from 'lodash';
+import {Col, Form, Input, message, Modal, Row, Select} from 'antd';
+import {service} from '@/pages/device/Instance';
+import type {DeviceInstance} from '../typings';
+import {useEffect, useState} from 'react';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import {UploadImage} from '@/components';
+import {debounce} from 'lodash';
 
 interface Props {
   visible: boolean;
@@ -119,8 +119,6 @@ const Save = (props: Props) => {
       })}
       confirmLoading={loading}
       onOk={handleSave}
-      permissionCode={'device/Instance'}
-      permission={'add'}
     >
       <Form
         form={form}

+ 24 - 36
src/pages/device/Instance/index.tsx

@@ -1,10 +1,10 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import type { DeviceInstance } from '@/pages/device/Instance/typings';
+import {PageContainer} from '@ant-design/pro-layout';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
+import type {DeviceInstance} from '@/pages/device/Instance/typings';
 import moment from 'moment';
-import { Badge, Button, Dropdown, Menu, message, Tooltip } from 'antd';
-import { useEffect, useRef, useState } from 'react';
-import { useHistory } from 'umi';
+import {Badge, Button, Dropdown, Menu, message, Tooltip} from 'antd';
+import {useEffect, useRef, useState} from 'react';
+import {useHistory, useIntl} from 'umi';
 import {
   CheckCircleOutlined,
   DeleteOutlined,
@@ -16,20 +16,20 @@ import {
   StopOutlined,
   SyncOutlined,
 } from '@ant-design/icons';
-import { model } from '@formily/reactive';
+import {model} from '@formily/reactive';
 import Service from '@/pages/device/Instance/service';
-import type { MetadataItem } from '@/pages/device/Product/typings';
-import { useIntl, connect } from 'umi';
+import type {MetadataItem} from '@/pages/device/Product/typings';
 import Save from './Save';
 import Export from './Export';
 import Import from './Import';
 import Process from './Process';
 import SearchComponent from '@/components/SearchComponent';
-import { ProTableCard, PermissionButton } from '@/components';
+import {PermissionButton, ProTableCard} from '@/components';
 import SystemConst from '@/utils/const';
 import Token from '@/utils/token';
 import DeviceCard from '@/components/ProTableCard/CardItems/device';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import {getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import {useLocation} from '@/hooks';
 
 export const statusMap = new Map();
 statusMap.set('在线', 'success');
@@ -54,7 +54,7 @@ export const InstanceModel = model<{
   params: new Set<string>(['test']),
 });
 export const service = new Service('device-instance');
-const Instance = (props: any) => {
+const Instance = () => {
   const actionRef = useRef<ActionType>();
   const [visible, setVisible] = useState<boolean>(false);
   const [exportVisible, setExportVisible] = useState<boolean>(false);
@@ -68,23 +68,22 @@ const Instance = (props: any) => {
   const history = useHistory<Record<string, string>>();
   const { permission } = PermissionButton.usePermission('device/Instance');
   const intl = useIntl();
+  const location = useLocation();
 
   useEffect(() => {
-    console.log(searchParams);
-    if (props.path) {
+    if (location.state) {
       const _terms: any[] = [];
-      Object.keys(props.locationState).forEach((key) => {
+      Object.keys(location.state).forEach((key) => {
         _terms.push({
           column: key,
-          value: props.locationState[key],
+          value: location.state[key],
         });
       });
       setSearchParams({
         terms: _terms,
       });
-      props.cleanLocationState();
     }
-  }, [props.path]);
+  }, [location]);
 
   const tools = (record: DeviceInstance) => [
     <Button
@@ -279,8 +278,8 @@ const Instance = (props: any) => {
     <Menu>
       <Menu.Item key="1">
         <PermissionButton
-          isPermission={permission.delete}
-          icon={<ExportOutlined />}
+          isPermission={permission.export}
+          icon={<ExportOutlined/>}
           type="default"
           onClick={() => {
             setExportVisible(true);
@@ -302,8 +301,8 @@ const Instance = (props: any) => {
       </Menu.Item>
       <Menu.Item key="4">
         <PermissionButton
-          isPermission={permission.active}
-          icon={<CheckCircleOutlined />}
+          isPermission={permission.action}
+          icon={<CheckCircleOutlined/>}
           type="primary"
           ghost
           popConfirm={{
@@ -323,8 +322,8 @@ const Instance = (props: any) => {
       </Menu.Item>
       <Menu.Item key="5">
         <PermissionButton
-          isPermission={permission.sync}
-          icon={<SyncOutlined />}
+          isPermission={true}
+          icon={<SyncOutlined/>}
           type="primary"
           onClick={() => {
             setType('sync');
@@ -590,15 +589,4 @@ const Instance = (props: any) => {
   );
 };
 
-const mapState = (state: any) => {
-  return {
-    locationState: state.location.locationState,
-    path: state.location.path,
-  };
-};
-
-const actionCreate = {
-  cleanLocationState: (payload: any) => ({ type: 'location/clean', payload }),
-};
-
-export default connect(mapState, actionCreate)(Instance);
+export default Instance;

+ 48 - 58
src/pages/device/Product/Detail/Access/index.tsx

@@ -1,18 +1,19 @@
-import { Badge, Button, Col, Empty, message, Row, Table, Tooltip } from 'antd';
-import { service } from '@/pages/link/AccessConfig';
-import { productModel, service as productService } from '@/pages/device/Product';
+import {Badge, Button, Col, Empty, message, Row, Table, Tooltip} from 'antd';
+import {service} from '@/pages/link/AccessConfig';
+import {productModel, service as productService} from '@/pages/device/Product';
 import styles from './index.less';
-import type { SetStateAction } from 'react';
-import { useEffect, useState } from 'react';
+import type {SetStateAction} from 'react';
+import {useEffect, useState} from 'react';
 import AccessConfig from './AccessConfig';
 import ReactMarkdown from 'react-markdown';
-import { Form, FormGrid, FormItem, FormLayout, Input, Password, PreviewText } from '@formily/antd';
-import type { ISchema } from '@formily/json-schema';
-import type { ConfigProperty } from '@/pages/device/Product/typings';
-import { createSchemaField } from '@formily/react';
-import { createForm } from '@formily/core';
-import { QuestionCircleOutlined } from '@ant-design/icons';
+import {Form, FormGrid, FormItem, FormLayout, Input, Password, PreviewText} from '@formily/antd';
+import type {ISchema} from '@formily/json-schema';
+import type {ConfigProperty} from '@/pages/device/Product/typings';
+import {createSchemaField} from '@formily/react';
+import {createForm} from '@formily/core';
+import {QuestionCircleOutlined} from '@ant-design/icons';
 import TitleComponent from '@/components/TitleComponent';
+import usePermissions from '@/hooks/permission';
 
 const componentMap = {
   string: 'Input',
@@ -25,6 +26,7 @@ const Access = () => {
   const [access, setAccess] = useState<any>();
   const [providers, setProviders] = useState<any[]>([]);
   const [networkList, setNetworkList] = useState<any[]>([]);
+  const {permission} = usePermissions('device/Product');
 
   const MetworkTypeMapping = new Map();
   MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
@@ -73,11 +75,7 @@ const Access = () => {
       key: 'group',
       ellipsis: true,
       align: 'center',
-      render: (text: any) => (
-        <Tooltip placement="top" title={text}>
-          {text}
-        </Tooltip>
-      ),
+      width: 100,
       onCell: (record: any, index: number) => {
         const list = (config?.routes || []).sort((a: any, b: any) => a - b) || [];
         const arr = list.filter((res: any) => {
@@ -85,9 +83,9 @@ const Access = () => {
           return res?.group == record?.group;
         });
         if (index == 0 || list[index - 1]?.group != record?.group) {
-          return { rowSpan: arr.length };
+          return {rowSpan: arr.length};
         } else {
-          return { rowSpan: 0 };
+          return {rowSpan: 0};
         }
       },
     },
@@ -98,7 +96,7 @@ const Access = () => {
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
-        <Tooltip placement="top" title={text}>
+        <Tooltip placement="topLeft" title={text}>
           {text}
         </Tooltip>
       ),
@@ -109,6 +107,7 @@ const Access = () => {
       key: 'stream',
       ellipsis: true,
       align: 'center',
+      width: 100,
       render: (text: any, record: any) => {
         const list = [];
         if (record?.upstream) {
@@ -127,7 +126,7 @@ const Access = () => {
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
-        <Tooltip placement="top" title={text}>
+        <Tooltip placement="topLeft" title={text}>
           {text}
         </Tooltip>
       ),
@@ -140,12 +139,8 @@ const Access = () => {
       dataIndex: 'group',
       key: 'group',
       ellipsis: true,
+      width: 100,
       align: 'center',
-      render: (text: any) => (
-        <Tooltip placement="top" title={text}>
-          {text}
-        </Tooltip>
-      ),
       onCell: (record: any, index: number) => {
         const list = (config?.routes || []).sort((a: any, b: any) => a - b) || [];
         const arr = list.filter((res: any) => {
@@ -153,7 +148,7 @@ const Access = () => {
           return res?.group == record?.group;
         });
         if (index == 0 || list[index - 1]?.group != record?.group) {
-          return { rowSpan: arr.length };
+          return {rowSpan: arr.length};
         } else {
           return { rowSpan: 0 };
         }
@@ -166,7 +161,7 @@ const Access = () => {
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
-        <Tooltip placement="top" title={text}>
+        <Tooltip placement="topLeft" title={text}>
           {text}
         </Tooltip>
       ),
@@ -178,7 +173,7 @@ const Access = () => {
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
-        <Tooltip placement="top" title={text}>
+        <Tooltip placement="topLeft" title={text}>
           {text}
         </Tooltip>
       ),
@@ -190,7 +185,7 @@ const Access = () => {
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
-        <Tooltip placement="top" title={text}>
+        <Tooltip placement="topLeft" title={text}>
           {text}
         </Tooltip>
       ),
@@ -352,17 +347,21 @@ const Access = () => {
         <div style={{ padding: '100px 0' }}>
           <Empty
             description={
-              <span>
-                请先
-                <a
-                  onClick={() => {
-                    setConfigVisible(true);
-                  }}
-                >
-                  选择
-                </a>
-                设备接入网关,用以提供设备接入能力
-              </span>
+              permission.add ? (
+                <span>
+                  请先
+                  <a
+                    onClick={() => {
+                      setConfigVisible(true);
+                    }}
+                  >
+                    选择
+                  </a>
+                  设备接入网关,用以提供设备接入能力
+                </span>
+              ) : (
+                '暂无权限,请联系管理员'
+              )
             }
           />
         </div>
@@ -424,18 +423,18 @@ const Access = () => {
                           />
                         </div>
                       ),
-                    )
+                  )
                   : '暂无连接信息'}
               </div>
 
               <div className={styles.item}>{renderConfigCard()}</div>
             </div>
           </Col>
-          <Col span={12}>
-            <div className={styles.info}>
-              {config?.routes && config?.routes?.length > 0 ? (
+          {config?.routes && config?.routes?.length > 0 && (
+            <Col span={12}>
+              <div className={styles.info}>
                 <div>
-                  <div style={{ fontWeight: '600', marginBottom: 10 }}>
+                  <div style={{fontWeight: '600', marginBottom: 10}}>
                     {access?.provider === 'mqtt-server-gateway' ||
                     access?.provider === 'mqtt-client-gateway'
                       ? 'topic'
@@ -446,21 +445,12 @@ const Access = () => {
                     bordered
                     columns={config.id === 'MQTT' ? columnsMQTT : columnsHTTP}
                     pagination={false}
-                    scroll={{ y: 500 }}
+                    scroll={{y: 500}}
                   />
                 </div>
-              ) : (
-                <Empty
-                  description={`暂无${
-                    access?.provider === 'mqtt-server-gateway' ||
-                    access?.provider === 'mqtt-client-gateway'
-                      ? 'topic'
-                      : 'URL信息'
-                  }`}
-                />
-              )}
-            </div>
-          </Col>
+              </div>
+            </Col>
+          )}
         </Row>
       )}
       {configVisible && (

+ 12 - 9
src/pages/device/Product/Detail/BaseInfo/index.tsx

@@ -1,10 +1,11 @@
-import { productModel, service } from '@/pages/device/Product';
-import { Button, Descriptions } from 'antd';
-import { useState } from 'react';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { EditOutlined } from '@ant-design/icons';
-import { getDateFormat } from '@/utils/util';
+import {productModel, service} from '@/pages/device/Product';
+import {Descriptions} from 'antd';
+import {useState} from 'react';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import {EditOutlined} from '@ant-design/icons';
+import {getDateFormat} from '@/utils/util';
 import Save from '@/pages/device/Product/Save';
+import {PermissionButton} from '@/components';
 
 // const componentMap = {
 //   string: 'Input',
@@ -16,6 +17,7 @@ const BaseInfo = () => {
   // const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
   // const [state, setState] = useState<boolean>(false);
   const [visible, setVisible] = useState(false);
+  const {permission} = PermissionButton.usePermission('device/Product');
 
   // const form = createForm({
   //   validateFirst: true,
@@ -152,15 +154,16 @@ const BaseInfo = () => {
         column={3}
         title={[
           <span key={1}>产品信息</span>,
-          <Button
+          <PermissionButton
+            isPermission={permission.update}
             key={2}
             type={'link'}
             onClick={() => {
               setVisible(true);
             }}
           >
-            <EditOutlined />
-          </Button>,
+            <EditOutlined/>
+          </PermissionButton>,
         ]}
         bordered
       >

+ 74 - 75
src/pages/device/Product/Detail/index.tsx

@@ -1,31 +1,21 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import { history, useLocation, useParams } from 'umi';
-import {
-  Badge,
-  Button,
-  Card,
-  Descriptions,
-  message,
-  Popconfirm,
-  Space,
-  Spin,
-  Switch,
-  Tooltip,
-} from 'antd';
+import {PageContainer} from '@ant-design/pro-layout';
+import {useIntl, useLocation, useParams} from 'umi';
+import {Badge, Card, Descriptions, message, Popconfirm, Space, Spin, Switch, Tooltip} from 'antd';
 import BaseInfo from '@/pages/device/Product/Detail/BaseInfo';
-import { observer } from '@formily/react';
-import { productModel, service } from '@/pages/device/Product';
-import { useCallback, useEffect, useState } from 'react';
-import { useIntl, connect } from 'umi';
+import {observer} from '@formily/react';
+import {productModel, service} from '@/pages/device/Product';
+import {useCallback, useEffect, useState} from 'react';
+import {useHistory} from '@/hooks';
 import Metadata from '@/pages/device/components/Metadata';
 import Access from '@/pages/device/Product/Detail/Access';
-import type { DeviceMetadata } from '@/pages/device/Product/typings';
-import { Store } from 'jetlinks-store';
+import type {DeviceMetadata} from '@/pages/device/Product/typings';
+import {Store} from 'jetlinks-store';
 import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
-import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import {getMenuPathByCode, MENUS_CODE} from '@/utils/menu';
 import encodeQuery from '@/utils/encodeQuery';
 import MetadataMap from '@/pages/device/Instance/Detail/MetadataMap';
 import SystemConst from '@/utils/const';
+import {PermissionButton} from '@/components';
 
 export const ModelEnum = {
   base: 'base',
@@ -33,12 +23,15 @@ export const ModelEnum = {
   access: 'access',
 };
 
-const ProductDetail = observer((props: any) => {
+const ProductDetail = observer(() => {
   const intl = useIntl();
   const [mode, setMode] = useState('base');
   const param = useParams<{ id: string }>();
   const [loading, setLoading] = useState<boolean>(false);
   const location = useLocation();
+  const history = useHistory();
+
+  const {permission} = PermissionButton.usePermission('device/Product');
 
   const statusMap = {
     1: {
@@ -194,7 +187,9 @@ const ProductDetail = observer((props: any) => {
       onBack={() => history.goBack()}
       extraContent={<Space size={24} />}
       onTabChange={(key) => {
-        setMode(key);
+        if (permission.update) {
+          setMode(key);
+        }
       }}
       tabList={list}
       tabActiveKey={mode}
@@ -202,63 +197,78 @@ const ProductDetail = observer((props: any) => {
         <Spin spinning={loading}>
           <Descriptions size="small" column={2}>
             <Descriptions.Item label={'设备数量'}>
-              <Button
+              <PermissionButton
                 type={'link'}
-                style={{ padding: 0, height: 'auto' }}
+                isPermission={!!getMenuPathByCode(MENUS_CODE['device/Instance'])}
+                style={{padding: 0, height: 'auto'}}
                 onClick={() => {
                   const url = getMenuPathByCode(MENUS_CODE['device/Instance']);
                   const params = {
                     productId: productModel.current?.id,
                   };
-                  props.push({
-                    locationState: params,
-                    path: url,
-                  });
-                  history.push(url, params);
+                  if (url) {
+                    history.push(url, params);
+                  }
                 }}
               >
                 {productModel.current?.count || 0}
-              </Button>
+              </PermissionButton>
             </Descriptions.Item>
           </Descriptions>
         </Spin>
       }
       title={productModel.current?.name}
       subTitle={
-        <Popconfirm
-          title={productModel.current?.state === 1 ? '确认取消发布' : '确认发布'}
-          onConfirm={() => {
-            changeDeploy(statusMap[productModel.current?.state || 0].action);
-          }}
-        >
-          <Switch
-            key={2}
-            checked={productModel.current?.state === 1}
-            checkedChildren="已发布"
-            unCheckedChildren="未发布"
-          />
-        </Popconfirm>
+        permission.update ? (
+          <Popconfirm
+            title={productModel.current?.state === 1 ? '确认取消发布' : '确认发布'}
+            onConfirm={() => {
+              changeDeploy(statusMap[productModel.current?.state || 0].action);
+            }}
+          >
+            <Switch
+              key={2}
+              checked={productModel.current?.state === 1}
+              checkedChildren="已发布"
+              unCheckedChildren="未发布"
+            />
+          </Popconfirm>
+        ) : (
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.noPermission',
+              defaultMessage: '没有权限',
+            })}
+          >
+            <Switch
+              key={2}
+              disabled
+              checked={productModel.current?.state === 1}
+              checkedChildren="已发布"
+              unCheckedChildren="未发布"
+            />
+          </Tooltip>
+        )
       }
       extra={[
-        <Popconfirm title={'确定应用配置?'} key="1" onConfirm={() => changeDeploy('deploy')}>
-          {productModel.current?.state === 0 ? (
-            <Tooltip title={'请先发布产品'}>
-              <Button disabled type="primary">
-                {intl.formatMessage({
-                  id: 'pages.device.productDetail.setting',
-                  defaultMessage: '应用配置',
-                })}
-              </Button>
-            </Tooltip>
-          ) : (
-            <Button key="1" type="primary">
-              {intl.formatMessage({
-                id: 'pages.device.productDetail.setting',
-                defaultMessage: '应用配置',
-              })}
-            </Button>
-          )}
-        </Popconfirm>,
+        <PermissionButton
+          key="1"
+          type={'primary'}
+          popConfirm={{
+            title: '确定应用配置?',
+            onConfirm: () => {
+              changeDeploy('deploy');
+            },
+          }}
+          tooltip={productModel.current?.state === 0 ? {title: '请先发布产品'} : undefined}
+          isPermission={permission.update}
+          disabled={productModel.current?.state === 0}
+        >
+          {intl.formatMessage({
+            id: 'pages.device.productDetail.setting',
+            defaultMessage: '应用配置',
+          })}
+        </PermissionButton>,
       ]}
     >
       <Card>
@@ -332,15 +342,4 @@ const ProductDetail = observer((props: any) => {
   );
 });
 
-const mapState = (state: any) => ({
-  state: state.state,
-  path: state.path,
-});
-
-const actionCreate = {
-  push: (payload: any) => {
-    return { type: 'location/push', payload };
-  },
-};
-
-export default connect(mapState, actionCreate)(ProductDetail);
+export default ProductDetail;

+ 38 - 26
src/pages/device/Product/index.tsx

@@ -1,6 +1,6 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import { Badge, Button, message, Space, Tooltip, Upload } from 'antd';
-import type { ProductItem } from '@/pages/device/Product/typings';
+import {PageContainer} from '@ant-design/pro-layout';
+import {Badge, Button, message, Space, Tooltip, Upload} from 'antd';
+import type {ProductItem} from '@/pages/device/Product/typings';
 import {
   DeleteOutlined,
   DownloadOutlined,
@@ -10,23 +10,23 @@ import {
   StopOutlined,
 } from '@ant-design/icons';
 import Service from '@/pages/device/Product/service';
-import { observer } from '@formily/react';
-import { model } from '@formily/reactive';
-import { useHistory } from 'umi';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { useEffect, useRef, useState } from 'react';
+import {observer} from '@formily/react';
+import {model} from '@formily/reactive';
+import {useHistory} from 'umi';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
+import {useEffect, useRef, useState} from 'react';
 import Save from '@/pages/device/Product/Save';
 import SearchComponent from '@/components/SearchComponent';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { ProTableCard, AIcon, PermissionButton } from '@/components';
+import {getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import {AIcon, PermissionButton, ProTableCard} from '@/components';
 import ProductCard from '@/components/ProTableCard/CardItems/product';
-import { downloadObject } from '@/utils/util';
+import {downloadObject} from '@/utils/util';
 
 export const service = new Service('device-product');
 export const statusMap = {
-  1: <Badge status="processing" text="已发布" />,
-  0: <Badge status="error" text="未发布" />,
+  1: <Badge status="processing" text="已发布"/>,
+  0: <Badge status="error" text="未发布"/>,
 };
 export const productModel = model<{
   current: ProductItem | undefined;
@@ -192,9 +192,10 @@ const Product = observer(() => {
       type={'link'}
       style={{ padding: 0 }}
       isPermission={permission.delete}
+      disabled={record.state === 1}
       popConfirm={{
         title: intl.formatMessage({
-          id: record.state === 1 ? 'pages.device.productDetail.deleteTip' : 'page.table.isDelete',
+          id: 'page.table.isDelete',
           defaultMessage: '是否删除?',
         }),
         onConfirm: async () => {
@@ -207,7 +208,10 @@ const Product = observer(() => {
       }}
       tooltip={{
         title: intl.formatMessage({
-          id: 'pages.data.option.remove',
+          id:
+            record.state === 1
+              ? 'pages.device.productDetail.deleteTip'
+              : 'pages.data.option.remove',
           defaultMessage: '删除',
         }),
       }}
@@ -359,6 +363,7 @@ const Product = observer(() => {
           <Upload
             disabled={!permission.import}
             key={'import'}
+            accept={'.json'}
             showUploadList={false}
             beforeUpload={(file) => {
               const reader = new FileReader();
@@ -366,25 +371,24 @@ const Product = observer(() => {
               reader.onload = async (result) => {
                 const text = result.target?.result as string;
                 if (!file.type.includes('json')) {
-                  message.warning('文件内容格式错误');
-                  return;
+                  message.error('请上传json格式文件');
+                  return false;
                 }
                 try {
                   const data = JSON.parse(text || '{}');
                   // 设置导入的产品状态为未发布
                   data.state = 0;
                   if (Array.isArray(data)) {
-                    message.warning('文件内容格式错误');
+                    message.error('请上传json格式文件');
                     return;
                   }
-
                   const res = await service.update(data);
                   if (res.status === 200) {
                     message.success('操作成功');
                     actionRef.current?.reload();
                   }
                 } catch {
-                  message.warning('文件内容格式错误');
+                  message.error('请上传json格式文件');
                 }
               };
               return false;
@@ -475,14 +479,22 @@ const Product = observer(() => {
               <PermissionButton
                 key="delete"
                 type={'link'}
-                style={{ padding: 0 }}
+                style={{padding: 0}}
                 isPermission={permission.delete}
+                disabled={record.state === 1}
+                tooltip={
+                  record.state === 1
+                    ? {
+                      title: intl.formatMessage({
+                        id: 'pages.device.productDetail.deleteTip',
+                        defaultMessage: '已发布的产品不能进行删除操作',
+                      }),
+                    }
+                    : undefined
+                }
                 popConfirm={{
                   title: intl.formatMessage({
-                    id:
-                      record.state === 1
-                        ? 'pages.device.productDetail.deleteTip'
-                        : 'page.table.isDelete',
+                    id: 'page.table.isDelete',
                     defaultMessage: '是否删除?',
                   }),
                   onConfirm: async () => {

+ 75 - 64
src/pages/device/components/Metadata/Base/index.tsx

@@ -1,30 +1,33 @@
-import type { ProColumns } from '@jetlinks/pro-table';
+import type {ProColumns} from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { useCallback, useEffect, useState } from 'react';
-import { useParams } from 'umi';
+import {useCallback, useEffect, useState} from 'react';
+import {useParams} from 'umi';
 import DB from '@/db';
-import type { MetadataItem, MetadataType } from '@/pages/device/Product/typings';
+import type {MetadataItem, MetadataType} from '@/pages/device/Product/typings';
 import MetadataMapping from './columns';
-import { Button, message, Popconfirm, Tooltip } from 'antd';
-import { DeleteOutlined, EditOutlined, ImportOutlined, PlusOutlined } from '@ant-design/icons';
+import {message} from 'antd';
+import {DeleteOutlined, EditOutlined, ImportOutlined, PlusOutlined} from '@ant-design/icons';
 import Edit from './Edit';
-import { observer } from '@formily/react';
+import {observer} from '@formily/react';
 import MetadataModel from './model';
-import { Store } from 'jetlinks-store';
+import {Store} from 'jetlinks-store';
 import SystemConst from '@/utils/const';
-import { useIntl } from '@@/plugin-locale/localeExports';
+import {useIntl} from '@@/plugin-locale/localeExports';
 import PropertyImport from '@/pages/device/Product/Detail/PropertyImport';
-import { productModel } from '@/pages/device/Product';
-import { InstanceModel } from '@/pages/device/Instance';
-import { asyncUpdateMedata, removeMetadata } from '../metadata';
+import {productModel} from '@/pages/device/Product';
+import {InstanceModel} from '@/pages/device/Instance';
+import {asyncUpdateMedata, removeMetadata} from '../metadata';
+import type {permissionType} from '@/hooks/permission';
+import {PermissionButton} from '@/components';
 
 interface Props {
   type: MetadataType;
   target: 'product' | 'device';
+  permission: Partial<permissionType>;
 }
 
 const BaseMetadata = observer((props: Props) => {
-  const { type, target = 'product' } = props;
+  const {type, target = 'product'} = props;
   const intl = useIntl();
   const param = useParams<{ id: string }>();
 
@@ -73,36 +76,41 @@ const BaseMetadata = observer((props: Props) => {
       align: 'center',
       width: 200,
       render: (_: unknown, record: MetadataItem) => [
-        <Tooltip key={'edit'} title={operateLimits('add', type) ? '暂不支持' : '编辑'}>
-          <Button
-            key="editable"
-            type="link"
-            disabled={operateLimits('updata', type)}
-            onClick={() => {
-              MetadataModel.edit = true;
-              MetadataModel.item = record;
-              MetadataModel.type = type;
-              MetadataModel.action = 'edit';
-            }}
-          >
-            <Tooltip title="编辑">
-              <EditOutlined />
-            </Tooltip>
-          </Button>
-        </Tooltip>,
-        ,
-        <a key="delete">
-          <Popconfirm
-            title="确认删除?"
-            onConfirm={async () => {
+        <PermissionButton
+          isPermission={props.permission.update}
+          type="link"
+          key={'edit'}
+          style={{padding: 0}}
+          disabled={operateLimits('updata', type)}
+          onClick={() => {
+            MetadataModel.edit = true;
+            MetadataModel.item = record;
+            MetadataModel.type = type;
+            MetadataModel.action = 'edit';
+          }}
+          tooltip={{
+            title: operateLimits('add', type) ? '暂不支持' : '编辑',
+          }}
+        >
+          <EditOutlined/>
+        </PermissionButton>,
+        <PermissionButton
+          isPermission={props.permission.update}
+          type="link"
+          key={'delete'}
+          style={{padding: 0}}
+          popConfirm={{
+            title: '确认删除?',
+            onConfirm: async () => {
               await removeItem(record);
-            }}
-          >
-            <Tooltip title="删除">
-              <DeleteOutlined />
-            </Tooltip>
-          </Popconfirm>
-        </a>,
+            },
+          }}
+          tooltip={{
+            title: '删除',
+          }}
+        >
+          <DeleteOutlined/>
+        </PermissionButton>,
       ],
     },
   ];
@@ -165,36 +173,39 @@ const BaseMetadata = observer((props: Props) => {
         }}
         toolBarRender={() => [
           props.type === 'properties' && (
-            <Button
+            <PermissionButton
+              isPermission={props.permission.update}
               onClick={() => {
                 MetadataModel.importMetadata = true;
               }}
               key="button"
-              icon={<ImportOutlined />}
+              icon={<ImportOutlined/>}
               type="ghost"
             >
               导入属性
-            </Button>
+            </PermissionButton>
           ),
-          <Tooltip key={'add'} title={operateLimits('add', type) ? '暂不支持' : '新增'}>
-            <Button
-              onClick={() => {
-                MetadataModel.edit = true;
-                MetadataModel.item = undefined;
-                MetadataModel.type = type;
-                MetadataModel.action = 'add';
-              }}
-              disabled={operateLimits('add', type)}
-              key="button"
-              icon={<PlusOutlined />}
-              type="primary"
-            >
-              {intl.formatMessage({
-                id: 'pages.searchTable.new',
-                defaultMessage: '新建',
-              })}
-            </Button>
-          </Tooltip>,
+          <PermissionButton
+            isPermission={props.permission.update}
+            key={'add'}
+            onClick={() => {
+              MetadataModel.edit = true;
+              MetadataModel.item = undefined;
+              MetadataModel.type = type;
+              MetadataModel.action = 'add';
+            }}
+            disabled={operateLimits('add', type)}
+            icon={<PlusOutlined/>}
+            type="primary"
+            tooltip={{
+              title: operateLimits('add', type) ? '暂不支持' : '新增',
+            }}
+          >
+            {intl.formatMessage({
+              id: 'pages.searchTable.new',
+              defaultMessage: '新建',
+            })}
+          </PermissionButton>,
         ]}
       />
       {MetadataModel.importMetadata && <PropertyImport type={target} />}

+ 21 - 17
src/pages/device/components/Metadata/index.tsx

@@ -1,15 +1,16 @@
-import { observer } from '@formily/react';
-import { Button, Space, Tabs } from 'antd';
+import {observer} from '@formily/react';
+import {Space, Tabs} from 'antd';
 import BaseMetadata from './Base';
-import { useIntl } from '@@/plugin-locale/localeExports';
+import {useIntl} from '@@/plugin-locale/localeExports';
 import Import from './Import';
-import type { ReactNode } from 'react';
-import { useState } from 'react';
+import type {ReactNode} from 'react';
+import {useState} from 'react';
 import Cat from './Cat';
 import Service from '@/pages/device/components/Metadata/service';
-import { InfoCircleOutlined } from '@ant-design/icons';
+import {InfoCircleOutlined} from '@ant-design/icons';
 import styles from './index.less';
-import { InstanceModel } from '@/pages/device/Instance';
+import {InstanceModel} from '@/pages/device/Instance';
+import {PermissionButton} from '@/components';
 
 interface Props {
   tabAction?: ReactNode;
@@ -22,11 +23,14 @@ const Metadata = observer((props: Props) => {
   const intl = useIntl();
   const [visible, setVisible] = useState<boolean>(false);
   const [cat, setCat] = useState<boolean>(false);
+  const {permission} = PermissionButton.usePermission(
+    props.type === 'device' ? 'device/Instance' : 'device/Product',
+  );
   console.log(InstanceModel.detail, 'test');
   return (
-    <div style={{ position: 'relative' }}>
+    <div style={{position: 'relative'}}>
       <div className={styles.tips}>
-        <InfoCircleOutlined style={{ marginRight: '3px' }} />
+        <InfoCircleOutlined style={{marginRight: '3px'}}/>
         {InstanceModel.detail?.independentMetadata
           ? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'
           : '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'}
@@ -36,19 +40,19 @@ const Metadata = observer((props: Props) => {
         tabBarExtraContent={
           <Space>
             {props?.tabAction}
-            <Button onClick={() => setVisible(true)}>
+            <PermissionButton isPermission={permission.update} onClick={() => setVisible(true)}>
               {intl.formatMessage({
                 id: 'pages.device.productDetail.metadata.quickImport',
                 defaultMessage: '快速导入',
               })}
-            </Button>
-            <Button onClick={() => setCat(true)}>
+            </PermissionButton>
+            <PermissionButton isPermission={permission.update} onClick={() => setCat(true)}>
               {intl.formatMessage({
                 id: 'pages.device.productDetail.metadata',
                 defaultMessage: '物模型',
               })}
               TSL
-            </Button>
+            </PermissionButton>
           </Space>
         }
         destroyInactiveTabPane
@@ -60,7 +64,7 @@ const Metadata = observer((props: Props) => {
           })}
           key="properties"
         >
-          <BaseMetadata target={props.type} type={'properties'} />
+          <BaseMetadata target={props.type} type={'properties'} permission={permission}/>
         </Tabs.TabPane>
         <Tabs.TabPane
           tab={intl.formatMessage({
@@ -69,7 +73,7 @@ const Metadata = observer((props: Props) => {
           })}
           key="functions"
         >
-          <BaseMetadata target={props.type} type={'functions'} />
+          <BaseMetadata target={props.type} type={'functions'} permission={permission}/>
         </Tabs.TabPane>
         <Tabs.TabPane
           tab={intl.formatMessage({
@@ -78,7 +82,7 @@ const Metadata = observer((props: Props) => {
           })}
           key="events"
         >
-          <BaseMetadata target={props.type} type={'events'} />
+          <BaseMetadata target={props.type} type={'events'} permission={permission}/>
         </Tabs.TabPane>
         <Tabs.TabPane
           tab={intl.formatMessage({
@@ -87,7 +91,7 @@ const Metadata = observer((props: Props) => {
           })}
           key="tags"
         >
-          <BaseMetadata target={props.type} type={'tags'} />
+          <BaseMetadata target={props.type} type={'tags'} permission={permission}/>
         </Tabs.TabPane>
       </Tabs>
       <Import visible={visible} close={() => setVisible(false)} />

+ 75 - 65
src/pages/link/AccessConfig/Detail/Access/index.tsx

@@ -1,26 +1,14 @@
-import {
-  Badge,
-  Button,
-  Card,
-  Col,
-  Empty,
-  Form,
-  Input,
-  message,
-  Row,
-  Steps,
-  Table,
-  Tooltip,
-} from 'antd';
-import { useEffect, useState } from 'react';
+import {Badge, Button, Card, Col, Empty, Form, Input, message, Row, Steps, Table, Tooltip,} from 'antd';
+import {useEffect, useState} from 'react';
 import styles from './index.less';
-import { service } from '@/pages/link/AccessConfig';
+import {service} from '@/pages/link/AccessConfig';
 import encodeQuery from '@/utils/encodeQuery';
-import { useHistory } from 'umi';
+import {useHistory} from 'umi';
 import ReactMarkdown from 'react-markdown';
-import { getButtonPermission, getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
-import { ExclamationCircleFilled } from '@ant-design/icons';
+import {getButtonPermission, getMenuPathByCode, MENUS_CODE} from '@/utils/menu';
+import {ExclamationCircleFilled} from '@ant-design/icons';
 import TitleComponent from '@/components/TitleComponent';
+import {PermissionButton} from '@/components';
 
 interface Props {
   change: () => void;
@@ -39,6 +27,8 @@ const Access = (props: Props) => {
   const [procotolCurrent, setProcotolCurrent] = useState<string>('');
   const [networkCurrent, setNetworkCurrent] = useState<string>('');
   const [config, setConfig] = useState<any>();
+  const networkPermission = PermissionButton.usePermission('link/Type').permission;
+  const protocolPermission = PermissionButton.usePermission('link/Protocol').permission;
 
   const MetworkTypeMapping = new Map();
   MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
@@ -75,8 +65,10 @@ const Access = (props: Props) => {
   };
 
   useEffect(() => {
-    if (props.provider?.id) {
-      queryNetworkList(props.provider?.id);
+    if (props.provider?.id && !props.data?.id) {
+      queryNetworkList(props.provider?.id, {
+        include: networkCurrent || '',
+      });
       setCurrent(0);
     }
   }, [props.provider]);
@@ -90,7 +82,9 @@ const Access = (props: Props) => {
         description: props.data?.description,
       });
       setCurrent(0);
-      queryNetworkList(props.data?.provider);
+      queryNetworkList(props.data?.provider, {
+        include: props.data?.channelId,
+      });
     }
   }, [props.data]);
 
@@ -142,6 +136,7 @@ const Access = (props: Props) => {
       key: 'group',
       ellipsis: true,
       align: 'center',
+      width: 80,
       render: (text: any) => (
         <Tooltip placement="top" title={text}>
           {text}
@@ -166,6 +161,7 @@ const Access = (props: Props) => {
       key: 'topic',
       ellipsis: true,
       align: 'center',
+      with: '30%',
       render: (text: any) => (
         <Tooltip placement="top" title={text}>
           {text}
@@ -177,6 +173,7 @@ const Access = (props: Props) => {
       dataIndex: 'stream',
       key: 'stream',
       ellipsis: true,
+      width: 80,
       align: 'center',
       render: (text: any, record: any) => {
         const list = [];
@@ -283,34 +280,38 @@ const Access = (props: Props) => {
                   queryNetworkList(
                     props.provider?.id,
                     encodeQuery({
+                      include: networkCurrent || '',
                       terms: {
                         name$LIKE: `%${value}%`,
                       },
                     }),
                   );
                 }}
-                style={{ width: 500, margin: '20px 0' }}
+                style={{width: 500, margin: '20px 0'}}
               />
-              <Button
-                type="primary"
-                disabled={getButtonPermission('link/Type', ['add'])}
+              <PermissionButton
+                isPermission={networkPermission.add}
                 onClick={() => {
                   const url = getMenuPathByCode(MENUS_CODE['link/Type/Detail']);
                   const tab: any = window.open(`${origin}/#${url}`);
                   tab!.onTabSaveSuccess = (value: any) => {
                     if (value.status === 200) {
-                      queryNetworkList(props.provider?.id);
+                      queryNetworkList(props.provider?.id, {
+                        include: networkCurrent || '',
+                      });
                     }
                   };
                 }}
+                key="button"
+                type="primary"
               >
                 新增
-              </Button>
+              </PermissionButton>
             </div>
             {networkList.length > 0 ? (
               <Row gutter={[16, 16]}>
                 {networkList.map((item) => (
-                  <Col key={item.name} span={8}>
+                  <Col key={item.id} span={8}>
                     <Card
                       className={styles.cardRender}
                       style={{
@@ -352,21 +353,26 @@ const Access = (props: Props) => {
                 description={
                   <span>
                     暂无数据
-                    <Button
-                      type="link"
-                      disabled={getButtonPermission('link/Type', ['add'])}
-                      onClick={() => {
-                        const url = getMenuPathByCode(MENUS_CODE['link/Type/Detail']);
-                        const tab: any = window.open(`${origin}/#${url}`);
-                        tab!.onTabSaveSuccess = (value: any) => {
-                          if (value.status === 200) {
-                            queryNetworkList(props.provider?.id);
-                          }
-                        };
-                      }}
-                    >
-                      创建接入方式
-                    </Button>
+                    {getButtonPermission('link/Type', ['add']) ? (
+                      '请联系管理员进行配置'
+                    ) : (
+                      <Button
+                        type="link"
+                        onClick={() => {
+                          const url = getMenuPathByCode(MENUS_CODE['link/Type/Detail']);
+                          const tab: any = window.open(`${origin}/#${url}`);
+                          tab!.onTabSaveSuccess = (value: any) => {
+                            if (value.status === 200) {
+                              queryNetworkList(props.provider?.id, {
+                                include: networkCurrent || '',
+                              });
+                            }
+                          };
+                        }}
+                      >
+                        创建接入方式
+                      </Button>
+                    )}
                   </span>
                 }
               />
@@ -394,11 +400,10 @@ const Access = (props: Props) => {
                     }),
                   );
                 }}
-                style={{ width: 500, margin: '20px 0' }}
+                style={{width: 500, margin: '20px 0'}}
               />
-              <Button
-                type="primary"
-                disabled={getButtonPermission('link/Protocol', ['add'])}
+              <PermissionButton
+                isPermission={protocolPermission.add}
                 onClick={() => {
                   const url = getMenuPathByCode(MENUS_CODE[`link/Protocol`]);
                   const tab: any = window.open(`${origin}/#${url}?save=true`);
@@ -408,14 +413,16 @@ const Access = (props: Props) => {
                     }
                   };
                 }}
+                key="button"
+                type="primary"
               >
                 新增
-              </Button>
+              </PermissionButton>
             </div>
             {procotolList.length > 0 ? (
               <Row gutter={[16, 16]}>
                 {procotolList.map((item) => (
-                  <Col key={item.name} span={8}>
+                  <Col key={item.id} span={8}>
                     <Card
                       className={styles.cardRender}
                       style={{
@@ -441,21 +448,24 @@ const Access = (props: Props) => {
                 description={
                   <span>
                     暂无数据
-                    <Button
-                      type="link"
-                      disabled={getButtonPermission('link/Protocol', ['add'])}
-                      onClick={() => {
-                        const url = getMenuPathByCode(MENUS_CODE[`link/Protocol`]);
-                        const tab: any = window.open(`${origin}/#${url}?save=true`);
-                        tab!.onTabSaveSuccess = (value: any) => {
-                          if (value) {
-                            queryProcotolList(props.provider?.id);
-                          }
-                        };
-                      }}
-                    >
-                      去新增
-                    </Button>
+                    {getButtonPermission('link/Protocol', ['add']) ? (
+                      '请联系管理员进行配置'
+                    ) : (
+                      <Button
+                        type="link"
+                        onClick={() => {
+                          const url = getMenuPathByCode(MENUS_CODE[`link/Protocol`]);
+                          const tab: any = window.open(`${origin}/#${url}?save=true`);
+                          tab!.onTabSaveSuccess = (value: any) => {
+                            if (value) {
+                              queryProcotolList(props.provider?.id);
+                            }
+                          };
+                        }}
+                      >
+                        去新增
+                      </Button>
+                    )}
                   </span>
                 }
               />

+ 20 - 11
src/pages/link/AccessConfig/Detail/Media/index.tsx

@@ -1,5 +1,5 @@
-import { Button, Card, Col, Form, Input, message, Row, Steps } from 'antd';
-import { useEffect, useState } from 'react';
+import {Button, Card, Col, Form, Input, message, Row, Steps} from 'antd';
+import {useEffect, useState} from 'react';
 import styles from './index.less';
 import {
   ArrayCollapse,
@@ -12,16 +12,16 @@ import {
   Radio,
   Select,
 } from '@formily/antd';
-import { createSchemaField } from '@formily/react';
-import type { ISchema } from '@formily/json-schema';
-import { createForm, registerValidateRules } from '@formily/core';
-import { service } from '@/pages/link/AccessConfig';
-import { useLocation } from 'umi';
+import {createSchemaField} from '@formily/react';
+import type {ISchema} from '@formily/json-schema';
+import {createForm, registerValidateRules} from '@formily/core';
+import {service} from '@/pages/link/AccessConfig';
+import {useLocation} from 'umi';
 import SipComponent from '@/components/SipComponent';
 import TitleComponent from '@/components/TitleComponent';
-import { ExclamationCircleFilled } from '@ant-design/icons';
-import { testIP } from '@/utils/util';
-import { getButtonPermission } from '@/utils/menu';
+import {ExclamationCircleFilled} from '@ant-design/icons';
+import {testIP} from '@/utils/util';
+import {getButtonPermission} from '@/utils/menu';
 
 type LocationType = {
   id?: string;
@@ -473,7 +473,16 @@ const Media = (props: Props) => {
                   }
                   if (resp.status === 200) {
                     message.success('操作成功!');
-                    history.back();
+                    if (params.get('save')) {
+                      if ((window as any).onTabSaveSuccess) {
+                        if (resp.result) {
+                          (window as any).onTabSaveSuccess(resp.result);
+                          setTimeout(() => window.close(), 300);
+                        }
+                      }
+                    } else {
+                      history.back();
+                    }
                   }
                 }}
               >

+ 14 - 6
src/pages/link/AccessConfig/Detail/index.tsx

@@ -1,11 +1,11 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import { useEffect, useState } from 'react';
-import { useLocation } from 'umi';
+import {PageContainer} from '@ant-design/pro-layout';
+import {useEffect, useState} from 'react';
+import {useLocation} from 'umi';
 import Access from './Access';
 import Provider from './Provider';
 import Media from './Media';
-import { service } from '@/pages/link/AccessConfig';
-import { Spin } from 'antd';
+import {service} from '@/pages/link/AccessConfig';
+import {Spin} from 'antd';
 
 type LocationType = {
   id?: string;
@@ -23,7 +23,10 @@ const Detail = () => {
 
   useEffect(() => {
     setLoading(true);
-    const id = new URLSearchParams(location.search).get('id') || undefined;
+    const _params = new URLSearchParams(location.search);
+    const id = _params.get('id') || undefined;
+    const paramsType = _params.get('type');
+
     service.getProviders().then((resp) => {
       if (resp.status === 200) {
         setDataSource(resp.result);
@@ -42,6 +45,11 @@ const Detail = () => {
               setType('network');
             }
           });
+        } else if (paramsType) {
+          setType('media');
+          setProvider(resp.result.find((item: any) => item.id === paramsType));
+          setData({});
+          setVisible(false);
         } else {
           setVisible(true);
         }

+ 10 - 10
src/pages/link/Certificate/index.tsx

@@ -1,14 +1,14 @@
-import { PageContainer } from '@ant-design/pro-layout';
+import {PageContainer} from '@ant-design/pro-layout';
 import BaseService from '@/utils/BaseService';
-import type { CertificateItem } from '@/pages/link/Certificate/typings';
-import { useRef } from 'react';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { message, Popconfirm, Tooltip } from 'antd';
-import { EditOutlined, MinusOutlined } from '@ant-design/icons';
+import type {CertificateItem} from '@/pages/link/Certificate/typings';
+import {useRef} from 'react';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
+import {message, Popconfirm, Tooltip} from 'antd';
+import {EditOutlined, MinusOutlined} from '@ant-design/icons';
 import BaseCrud from '@/components/BaseCrud';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import type { ISchema } from '@formily/json-schema';
-import { CurdModel } from '@/components/BaseCrud/model';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import type {ISchema} from '@formily/json-schema';
+import {CurdModel} from '@/components/BaseCrud/model';
 
 export const service = new BaseService<CertificateItem>('network/certificate');
 const Certificate = () => {
@@ -175,7 +175,7 @@ const Certificate = () => {
         },
       },
       description: {
-        title: '描述',
+        title: '说明',
         'x-component': 'Input.TextArea',
         'x-decorator': 'FormItem',
       },

+ 13 - 13
src/pages/link/Gateway/index.tsx

@@ -1,8 +1,8 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import type { GatewayItem } from '@/pages/link/Gateway/typings';
-import { useRef } from 'react';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { message, Popconfirm, Tooltip } from 'antd';
+import {PageContainer} from '@ant-design/pro-layout';
+import type {GatewayItem} from '@/pages/link/Gateway/typings';
+import {useRef} from 'react';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
+import {message, Popconfirm, Tooltip} from 'antd';
 import {
   EditOutlined,
   MinusOutlined,
@@ -12,14 +12,14 @@ import {
   StopOutlined,
 } from '@ant-design/icons';
 import BaseCrud from '@/components/BaseCrud';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import type { ISchema } from '@formily/json-schema';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import type {ISchema} from '@formily/json-schema';
 import Service from '@/pages/link/Gateway/service';
-import { CurdModel } from '@/components/BaseCrud/model';
-import type { Field, FormPathPattern } from '@formily/core';
-import { action } from '@formily/reactive';
-import { onFieldReact, onFieldValueChange } from '@formily/core';
-import { useAsyncDataSource } from '@/utils/util';
+import {CurdModel} from '@/components/BaseCrud/model';
+import type {Field, FormPathPattern} from '@formily/core';
+import {onFieldReact, onFieldValueChange} from '@formily/core';
+import {action} from '@formily/reactive';
+import {useAsyncDataSource} from '@/utils/util';
 
 export const service = new Service('gateway/device');
 
@@ -347,7 +347,7 @@ const Gateway = () => {
         'x-decorator': 'FormItem',
       },
       describe: {
-        title: '描述',
+        title: '说明',
         'x-component': 'Input.TextArea',
         'x-decorator': 'FormItem',
         'x-component-props': {

+ 170 - 266
src/pages/link/Protocol/index.tsx

@@ -1,26 +1,25 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import type { ProtocolItem } from '@/pages/link/Protocol/typings';
-import { useEffect, useRef } from 'react';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Badge, Button, message } from 'antd';
-import { CheckCircleOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
-import BaseCrud from '@/components/BaseCrud';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import type { ISchema } from '@formily/json-schema';
-import { CurdModel } from '@/components/BaseCrud/model';
+import {PageContainer} from '@ant-design/pro-layout';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
+import type {ProtocolItem} from '@/pages/link/Protocol/typings';
+import {Badge, message} from 'antd';
+import {useRef, useState} from 'react';
+import {CheckCircleOutlined, DeleteOutlined, EditOutlined, PlusOutlined, StopOutlined,} from '@ant-design/icons';
 import Service from '@/pages/link/Protocol/service';
-import { onFormValuesChange, registerValidateRules } from '@formily/core';
-import { Store } from 'jetlinks-store';
-import { useLocation } from 'umi';
-import SystemConst from '@/utils/const';
-import { getButtonPermission } from '@/utils/menu';
-import { PermissionButton } from '@/components';
+import {useIntl} from 'umi';
+import SearchComponent from '@/components/SearchComponent';
+import {PermissionButton, ProTableCard} from '@/components';
+import ProcotolCard from '@/components/ProTableCard/CardItems/protocol';
+import Save from './save';
 
 export const service = new Service('protocol');
+
 const Protocol = () => {
-  const intl = useIntl();
   const actionRef = useRef<ActionType>();
-  const { permission } = PermissionButton.usePermission('link/Protocol');
+  const [visible, setVisible] = useState<boolean>(false);
+  const [current, setCurrent] = useState<ProtocolItem | undefined>();
+  const [searchParams, setSearchParams] = useState<any>({});
+  const {permission} = PermissionButton.usePermission('link/Protocol');
+  const intl = useIntl();
 
   const modifyState = async (id: string, type: 'deploy' | 'un-deploy') => {
     const resp = await service.modifyState(id, type);
@@ -71,17 +70,16 @@ const Protocol = () => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      width: 200,
       render: (text, record) => [
         <PermissionButton
           isPermission={permission.update}
           key="edit"
           onClick={() => {
-            CurdModel.update(record);
-            CurdModel.model = 'edit';
+            setCurrent(record);
+            setVisible(true);
           }}
           type={'link'}
-          style={{ padding: 0 }}
+          style={{padding: 0}}
           tooltip={{
             title: intl.formatMessage({
               id: 'pages.data.option.edit',
@@ -89,45 +87,35 @@ const Protocol = () => {
             }),
           }}
         >
-          <EditOutlined />
+          <EditOutlined/>
+        </PermissionButton>,
+        <PermissionButton
+          isPermission={permission.action}
+          key="action"
+          type={'link'}
+          style={{padding: 0}}
+          tooltip={{
+            title: record.state === 1 ? '撤销' : '发布',
+          }}
+          popConfirm={{
+            title: `确认${record.state === 1 ? '撤销' : '发布'}`,
+            onConfirm: () => {
+              if (record.state === 1) {
+                modifyState(record.id, 'un-deploy');
+              } else {
+                modifyState(record.id, 'deploy');
+              }
+            },
+          }}
+        >
+          {record.state === 1 ? <StopOutlined/> : <CheckCircleOutlined/>}
         </PermissionButton>,
-        record.state !== 1 && (
-          <PermissionButton
-            isPermission={permission.action}
-            key="publish"
-            onClick={() => {
-              modifyState(record.id, 'deploy');
-            }}
-            type={'link'}
-            style={{ padding: 0 }}
-            tooltip={{
-              title: '发布',
-            }}
-          >
-            <CheckCircleOutlined />
-          </PermissionButton>
-        ),
-        record.state === 1 && (
-          <PermissionButton
-            isPermission={permission.action}
-            key="publish"
-            onClick={() => {
-              modifyState(record.id, 'un-deploy');
-            }}
-            type={'link'}
-            style={{ padding: 0 }}
-            tooltip={{
-              title: '撤销',
-            }}
-          >
-            <CheckCircleOutlined />
-          </PermissionButton>
-        ),
         <PermissionButton
           isPermission={permission.delete}
           tooltip={{
             title: record.state !== 1 ? '删除' : '请先禁用该协议,再删除',
           }}
+          style={{padding: 0}}
           disabled={record.state === 1}
           popConfirm={{
             title: '确认删除',
@@ -155,223 +143,139 @@ const Protocol = () => {
     },
   ];
 
-  registerValidateRules({
-    validateId(value) {
-      if (!value) return '';
-      const reg = new RegExp('^[0-9a-zA-Z_\\\\-]+$');
-      return reg.exec(value) ? '' : 'ID只能由数字、26个英文字母或者下划线组成';
-    },
-  });
-
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      layout: {
-        type: 'void',
-        'x-component': 'FormGrid',
-        'x-component-props': {
-          maxColumns: 1,
-          minColumns: 1,
-        },
-        properties: {
-          id: {
-            title: 'ID',
-            'x-component': 'Input',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-            },
-            'x-validator': [
-              {
-                required: true,
-                message: '请输入ID',
-              },
-              {
-                max: 64,
-                message: '最多可输入64个字符',
-              },
-              {
-                validateId: true,
-                message: 'ID只能由数字、26个英文字母或者下划线组成',
-              },
-              {
-                triggerType: 'onBlur',
-                validator: (value: string) => {
-                  if (!value) return;
-                  return new Promise((resolve) => {
-                    service
-                      .validator(value)
-                      .then((resp) => {
-                        if (!!resp?.result) {
-                          resolve('ID已存在');
-                        } else {
-                          resolve('');
-                        }
-                      })
-                      .catch(() => '验证失败!');
-                  });
-                },
-              },
-            ],
-            'x-component-props': {
-              placeholder: '请输入ID',
-            },
-          },
-          name: {
-            title: '名称',
-            'x-component': 'Input',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-            },
-            'x-component-props': {
-              placeholder: '请输入名称',
-            },
-            'x-validator': [
-              {
-                required: true,
-                message: '请输入名称',
-              },
-              {
-                max: 64,
-                message: '最多可输入64个字符',
-              },
-            ],
-          },
-          type: {
-            title: '类型',
-            'x-component': 'Select',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              tooltip: <div>jar:上传协议jar包,文件格式支持.jar或.zip</div>,
-            },
-            'x-component-props': {
-              placeholder: '请选择类型',
-            },
-            'x-validator': [
-              {
-                required: true,
-                message: '请选择类型',
-              },
-            ],
-            enum: [
-              { label: 'jar', value: 'jar' },
-              { label: 'local', value: 'local' },
-              // { label: 'script', value: 'script' },
-            ],
-          },
-          configuration: {
-            type: 'object',
-            properties: {
-              location: {
-                title: '文件地址',
-                'x-decorator': 'FormItem',
-                'x-visible': false,
-                'x-decorator-props': {
-                  tooltip: (
-                    <div>
-                      local:填写本地协议编译目录绝对地址,如:d:/workspace/protocol/target/classes
-                    </div>
-                  ),
-                },
-                'x-validator': [
-                  {
-                    required: true,
-                    message: '请输入文件地址',
-                  },
-                ],
-                'x-reactions': {
-                  dependencies: ['..type'],
-                  fulfill: {
-                    state: {
-                      visible: '{{["jar","local"].includes($deps[0])}}',
-                      componentType: '{{$deps[0]==="jar"?"FileUpload":"Input"}}',
-                      componentProps:
-                        '{{$deps[0]==="jar"?{type:"file", accept: ".jar, .zip"}:{placeholder: "请输入文件地址"}}}',
-                    },
-                  },
-                },
-              },
-            },
-          },
-          description: {
-            title: '说明',
-            'x-component': 'Input.TextArea',
-            'x-decorator': 'FormItem',
-            'x-component-props': {
-              rows: 3,
-              showCount: true,
-              maxLength: 200,
-            },
-          },
-        },
-      },
-    },
-  };
-
-  const location = useLocation();
-
-  useEffect(() => {
-    if ((location as any).query?.save === 'true') {
-      CurdModel.add();
-    }
-    const subscription = Store.subscribe(SystemConst.BASE_UPDATE_DATA, (data) => {
-      if ((window as any).onTabSaveSuccess) {
-        (window as any).onTabSaveSuccess(data);
-        setTimeout(() => window.close(), 300);
-      }
-    });
-    return () => subscription.unsubscribe();
-  }, []);
-
   return (
     <PageContainer>
-      <BaseCrud
+      <SearchComponent<ProtocolItem>
+        field={columns}
+        target="Protocol"
+        onSearch={(data) => {
+          actionRef.current?.reset?.();
+          setSearchParams(data);
+        }}
+      />
+      <ProTableCard<ProtocolItem>
         columns={columns}
-        service={service}
-        title={'插件管理'}
-        search={false}
-        modelConfig={{ width: '550px' }}
-        schema={schema}
         actionRef={actionRef}
-        disableAdd={getButtonPermission('link/Protocol', ['add'])}
-        formEffect={() => {
-          onFormValuesChange((form) => {
-            form.setFieldState('id', (state) => {
-              state.disabled = CurdModel.model === 'edit';
-            });
-            form.setFieldState('type', (state) => {
-              state.disabled = CurdModel.model === 'edit';
-            });
-          });
-        }}
-        footer={
-          <>
-            <Button onClick={CurdModel.close}>取消</Button>
-            <Button
-              type="primary"
-              onClick={() => {
-                Store.set('save-data', true);
-              }}
-            >
-              保存
-            </Button>
-            <Button
-              type="primary"
-              onClick={() => {
-                Store.set('save-data', async (data: any) => {
-                  // 获取到的保存的数据
-                  if (data.id) {
-                    await modifyState(data.id, 'deploy');
-                  }
-                });
-              }}
-            >
-              保存并发布
-            </Button>
-          </>
+        params={searchParams}
+        options={{fullScreen: true}}
+        request={(params) =>
+          service.query({
+            ...params,
+            sorts: [
+              {
+                name: 'createTime',
+                order: 'desc',
+              },
+            ],
+          })
         }
+        rowKey="id"
+        search={false}
+        pagination={{pageSize: 10}}
+        headerTitle={[
+          <PermissionButton
+            onClick={() => {
+              setVisible(true);
+              setCurrent({});
+            }}
+            style={{marginRight: 12}}
+            isPermission={permission.add}
+            key="button"
+            icon={<PlusOutlined/>}
+            type="primary"
+          >
+            {intl.formatMessage({
+              id: 'pages.data.option.add',
+              defaultMessage: '新增',
+            })}
+          </PermissionButton>,
+        ]}
+        cardRender={(record) => (
+          <ProcotolCard
+            {...record}
+            actions={[
+              <PermissionButton
+                isPermission={permission.update}
+                key="edit"
+                onClick={() => {
+                  setCurrent(record);
+                  setVisible(true);
+                }}
+                type={'link'}
+                style={{padding: 0}}
+                tooltip={{
+                  title: intl.formatMessage({
+                    id: 'pages.data.option.edit',
+                    defaultMessage: '编辑',
+                  }),
+                }}
+              >
+                <EditOutlined/>
+              </PermissionButton>,
+              <PermissionButton
+                isPermission={permission.action}
+                key="action"
+                type={'link'}
+                style={{padding: 0}}
+                tooltip={{
+                  title: record.state === 1 ? '撤销' : '发布',
+                }}
+                popConfirm={{
+                  title: `确认${record.state === 1 ? '撤销' : '发布'}`,
+                  onConfirm: () => {
+                    if (record.state === 1) {
+                      modifyState(record.id, 'un-deploy');
+                    } else {
+                      modifyState(record.id, 'deploy');
+                    }
+                  },
+                }}
+              >
+                {record.state === 1 ? <StopOutlined/> : <CheckCircleOutlined/>}
+              </PermissionButton>,
+              <PermissionButton
+                isPermission={permission.delete}
+                tooltip={{
+                  title: record.state !== 1 ? '删除' : '请先禁用该协议,再删除',
+                }}
+                disabled={record.state === 1}
+                popConfirm={{
+                  title: '确认删除',
+                  onConfirm: async () => {
+                    const resp: any = await service.remove(record.id);
+                    if (resp.status === 200) {
+                      message.success(
+                        intl.formatMessage({
+                          id: 'pages.data.option.success',
+                          defaultMessage: '操作成功!',
+                        }),
+                      );
+                      actionRef.current?.reload();
+                    } else {
+                      message.error(resp?.message || '操作失败');
+                    }
+                  },
+                }}
+                key="delete"
+                type="link"
+              >
+                <DeleteOutlined/>
+              </PermissionButton>,
+            ]}
+          />
+        )}
       />
+      {visible && (
+        <Save
+          data={current}
+          close={() => {
+            setVisible(false);
+          }}
+          reload={() => {
+            actionRef.current?.reload();
+          }}
+        />
+      )}
     </PageContainer>
   );
 };

+ 256 - 0
src/pages/link/Protocol/save/index.tsx

@@ -0,0 +1,256 @@
+import {Button, message} from 'antd';
+import {createForm, registerValidateRules} from '@formily/core';
+import {createSchemaField} from '@formily/react';
+import React, {useEffect, useState} from 'react';
+import * as ICONS from '@ant-design/icons';
+import {Form, FormGrid, FormItem, Input, Select} from '@formily/antd';
+import type {ISchema} from '@formily/json-schema';
+import {service} from '@/pages/link/Protocol';
+import {Modal} from '@/components';
+import FileUpload from '../FileUpload';
+import type {ProtocolItem} from '@/pages/link/Protocol/typings';
+
+interface Props {
+  data: ProtocolItem | undefined;
+  close: () => void;
+  reload: () => void;
+}
+
+const Save = (props: Props) => {
+  const [data, setData] = useState<ProtocolItem | undefined>(props.data);
+
+  useEffect(() => {
+    setData(props.data);
+  }, [props.data]);
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data || {},
+  });
+
+  registerValidateRules({
+    validateId(value) {
+      if (!value) return '';
+      const reg = new RegExp('^[0-9a-zA-Z_\\\\-]+$');
+      return reg.exec(value) ? '' : 'ID只能由数字、26个英文字母或者下划线组成';
+    },
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      FileUpload,
+      FormGrid,
+    },
+    scope: {
+      icon(name: any) {
+        return React.createElement(ICONS[name]);
+      },
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-component': 'FormGrid',
+        'x-component-props': {
+          maxColumns: 1,
+          minColumns: 1,
+        },
+        properties: {
+          id: {
+            title: 'ID',
+            'x-component': 'Input',
+            'x-decorator': 'FormItem',
+            'x-disabled': !!props.data?.id,
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入ID',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                validateId: true,
+                message: 'ID只能由数字、26个英文字母或者下划线组成',
+              },
+              {
+                triggerType: 'onBlur',
+                validator: (value: string) => {
+                  if (!value) return;
+                  return new Promise((resolve) => {
+                    service
+                      .validator(value)
+                      .then((resp) => {
+                        if (!!resp?.result) {
+                          resolve('ID已存在');
+                        } else {
+                          resolve('');
+                        }
+                      })
+                      .catch(() => '验证失败!');
+                  });
+                },
+              },
+            ],
+            'x-component-props': {
+              placeholder: '请输入ID',
+            },
+          },
+          name: {
+            title: '名称',
+            'x-component': 'Input',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入名称',
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入名称',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
+          },
+          type: {
+            title: '类型',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            'x-disabled': !!props.data?.id,
+            'x-decorator-props': {
+              tooltip: <div>jar:上传协议jar包,文件格式支持.jar或.zip</div>,
+            },
+            'x-component-props': {
+              placeholder: '请选择类型',
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择类型',
+              },
+            ],
+            enum: [
+              {label: 'jar', value: 'jar'},
+              {label: 'local', value: 'local'},
+              // { label: 'script', value: 'script' },
+            ],
+          },
+          configuration: {
+            type: 'object',
+            properties: {
+              location: {
+                title: '文件地址',
+                'x-decorator': 'FormItem',
+                'x-visible': false,
+                'x-decorator-props': {
+                  tooltip: (
+                    <div>
+                      local:填写本地协议编译目录绝对地址,如:d:/workspace/protocol/target/classes
+                    </div>
+                  ),
+                },
+                'x-validator': [
+                  {
+                    required: true,
+                    message: '请输入文件地址',
+                  },
+                ],
+                'x-reactions': {
+                  dependencies: ['..type'],
+                  fulfill: {
+                    state: {
+                      visible: '{{["jar","local"].includes($deps[0])}}',
+                      componentType: '{{$deps[0]==="jar"?"FileUpload":"Input"}}',
+                      componentProps:
+                        '{{$deps[0]==="jar"?{type:"file", accept: ".jar, .zip"}:{placeholder: "请输入文件地址"}}}',
+                    },
+                  },
+                },
+              },
+            },
+          },
+          description: {
+            title: '说明',
+            'x-component': 'Input.TextArea',
+            'x-decorator': 'FormItem',
+            'x-component-props': {
+              rows: 3,
+              showCount: true,
+              maxLength: 200,
+            },
+          },
+        },
+      },
+    },
+  };
+
+  const save = async (deploy: boolean) => {
+    const value = await form.submit<ProtocolItem>();
+    let response = undefined;
+    if (props.data?.id) {
+      response = await service.save(value);
+    } else {
+      response = await service.update(value);
+    }
+    if (response && response.status === 200) {
+      message.success('操作成功');
+      if (deploy) {
+        await service.modifyState(value.id, 'deploy');
+      }
+      props.reload();
+    }
+  };
+
+  return (
+    <Modal
+      title={props?.data?.id ? '编辑' : '新增'}
+      maskClosable={false}
+      visible
+      onCancel={props.close}
+      width={700}
+      permissionCode={'link/Protocol'}
+      permission={['add', 'edit']}
+      footer={
+        <>
+          <Button onClick={props.close}>取消</Button>
+          <Button
+            type="primary"
+            onClick={() => {
+              save(false);
+            }}
+          >
+            保存
+          </Button>
+          <Button
+            type="primary"
+            onClick={() => {
+              save(true);
+            }}
+          >
+            保存并发布
+          </Button>
+        </>
+      }
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema}/>
+      </Form>
+    </Modal>
+  );
+};
+export default Save;

+ 14 - 11
src/pages/media/Device/Channel/index.tsx

@@ -1,14 +1,14 @@
 // 视频设备通道列表
-import { PageContainer } from '@ant-design/pro-layout';
-import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import {PageContainer} from '@ant-design/pro-layout';
+import ProTable, {ActionType, ProColumns} from '@jetlinks/pro-table';
 import SearchComponent from '@/components/SearchComponent';
 import './index.less';
-import { useEffect, useRef, useState } from 'react';
-import { ChannelItem } from '@/pages/media/Device/Channel/typings';
-import { useHistory, useIntl, useLocation } from 'umi';
-import { BadgeStatus } from '@/components';
-import { StatusColorEnum } from '@/components/BadgeStatus';
-import { Button, message, Popconfirm, Tooltip } from 'antd';
+import {useEffect, useRef, useState} from 'react';
+import {ChannelItem} from '@/pages/media/Device/Channel/typings';
+import {useHistory, useIntl, useLocation} from 'umi';
+import {BadgeStatus} from '@/components';
+import {StatusColorEnum} from '@/components/BadgeStatus';
+import {Button, message, Popconfirm, Tooltip} from 'antd';
 import {
   DeleteOutlined,
   EditOutlined,
@@ -18,9 +18,9 @@ import {
 } from '@ant-design/icons';
 import Save from './Save';
 import Service from './service';
-import { ProviderValue } from '../index';
+import {ProviderValue} from '../index';
 import Live from './Live';
-import { getButtonPermission, getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import {getButtonPermission, getMenuPathByCode, MENUS_CODE} from '@/utils/menu';
 import Tree from './Tree';
 
 export const service = new Service('media');
@@ -239,7 +239,10 @@ export default () => {
             search={false}
             headerTitle={[
               type === ProviderValue.GB281 ? (
-                <Tooltip key="button" title={'接入方式为GB/T28281时,不支持新增'}>
+                <Tooltip
+                  key="button"
+                  title={<div style={{width: 265}}>接入方式为GB/T28281时,不支持新增</div>}
+                >
                   <Button disabled>
                     {intl.formatMessage({
                       id: 'pages.data.option.add',

+ 119 - 52
src/pages/media/Device/Save/ProviderSelect.tsx

@@ -1,13 +1,20 @@
 import classNames from 'classnames';
-import { Badge } from 'antd';
-import { StatusColorEnum } from '@/components/BadgeStatus';
+import {Badge, Button, Empty} from 'antd';
+import {StatusColorEnum} from '@/components/BadgeStatus';
 import styles from '@/pages/link/AccessConfig/index.less';
-import { TableCard } from '@/components';
+import {TableCard} from '@/components';
 import './providerSelect.less';
+import usePermissions from '@/hooks/permission';
+import {useIntl} from 'umi';
+import {providerType, service} from '@/pages/media/Device';
+import {useCallback, useEffect, useRef, useState} from 'react';
+import {getMenuPathByCode, MENUS_CODE} from '@/utils/menu';
+import {useRequest} from '@@/plugin-request/request';
 
 interface ProviderProps {
   value?: string;
   options?: any[];
+  type: string;
   onChange?: (id: string) => void;
   onSelect?: (id: string, rowData: any) => void;
 }
@@ -15,64 +22,124 @@ interface ProviderProps {
 const defaultImage = require('/public/images/device-access.png');
 
 export default (props: ProviderProps) => {
+  const {permission} = usePermissions('link/AccessConfig');
+  const [options, setOptions] = useState<any[]>([]);
+  const addItemKey = useRef('');
+  const intl = useIntl();
+
+  const itemClick = useCallback(
+    (item: any) => {
+      if (props.onChange) {
+        props.onChange(item.id);
+      }
+
+      if (props.onSelect) {
+        props.onSelect(item.id, item);
+      }
+    },
+    [props],
+  );
+
+  const {run: getProviderList} = useRequest(service.queryProvider, {
+    manual: true,
+    formatResult: (res) => res.result,
+    onSuccess: (resp) => {
+      if (resp.data && resp.data.length) {
+        setOptions(resp.data);
+        if (addItemKey.current) {
+          const _item = resp.data.find((item: any) => item.id === addItemKey.current);
+          itemClick(_item);
+          addItemKey.current = '';
+        }
+      }
+    },
+  });
+
+  const jumpPage = useCallback(() => {
+    const url = getMenuPathByCode(MENUS_CODE['link/AccessConfig/Detail']);
+    const tab: any = window.open(`${origin}/#${url}?save=true&type=${props.type}`);
+    tab!.onTabSaveSuccess = (value: any) => {
+      addItemKey.current = value.id;
+      getProviderList({
+        sorts: [{name: 'createTime', value: 'asc'}],
+        terms: [{column: 'provider', value: props.type}],
+        pageSize: 100,
+      });
+    };
+  }, [props.type]);
+
+  useEffect(() => {
+    setOptions(props.options || []);
+  }, [props.options]);
+
+  const emptyDescription = permission.add ? (
+    <>
+      暂无数据,请先
+      <Button type={'link'} onClick={jumpPage} style={{padding: 0}}>
+        添加{providerType[props.type]} 接入网关
+      </Button>
+    </>
+  ) : (
+    intl.formatMessage({
+      id: 'pages.data.option.noPermission',
+      defaultMessage: '没有权限',
+    })
+  );
+
   return (
     <div className={'provider-list'}>
-      {props.options && props.options.length
-        ? props.options.map((item) => (
-            <div
-              onClick={() => {
-                if (props.onChange) {
-                  props.onChange(item.id);
-                }
-
-                if (props.onSelect) {
-                  props.onSelect(item.id, item);
-                }
+      {options && options.length ? (
+        options.map((item) => (
+          <div
+            onClick={() => {
+              itemClick(item);
+            }}
+            style={{padding: 16}}
+          >
+            <TableCard
+              className={classNames({active: item.id === props.value})}
+              showMask={false}
+              showTool={false}
+              status={item.state.value}
+              statusText={item.state.text}
+              statusNames={{
+                enabled: StatusColorEnum.processing,
+                disabled: StatusColorEnum.error,
               }}
-              style={{ padding: 16 }}
             >
-              <TableCard
-                className={classNames({ active: item.id === props.value })}
-                showMask={false}
-                showTool={false}
-                status={item.state.value}
-                statusText={item.state.text}
-                statusNames={{
-                  enabled: StatusColorEnum.processing,
-                  disabled: StatusColorEnum.error,
-                }}
-              >
-                <div className={styles.context}>
-                  <div>
-                    <img width={88} height={88} src={defaultImage} alt={''} />
+              <div className={styles.context}>
+                <div>
+                  <img width={88} height={88} src={defaultImage} alt={''}/>
+                </div>
+                <div className={styles.card}>
+                  <div className={styles.header}>
+                    <div className={styles.title}>{item.name || '--'}</div>
+                    <div className={styles.desc}>{item.description || '--'}</div>
                   </div>
-                  <div className={styles.card}>
-                    <div className={styles.header}>
-                      <div className={styles.title}>{item.name || '--'}</div>
-                      <div className={styles.desc}>{item.description || '--'}</div>
-                    </div>
-                    <div className={styles.container}>
-                      <div className={styles.server}>
-                        <div className={styles.subTitle}>{item?.channelInfo?.name || '--'}</div>
-                        <div style={{ width: '100%' }}>
-                          {item.channelInfo?.addresses.map((i: any, index: number) => (
-                            <p key={i.address + `_address${index}`}>
-                              <Badge color={i.health === -1 ? 'red' : 'green'} text={i.address} />
-                            </p>
-                          ))}
-                        </div>
-                      </div>
-                      <div className={styles.procotol}>
-                        <div className={styles.subTitle}>{item?.protocolDetail?.name || '--'}</div>
-                        <p>{item.protocolDetail?.description || '--'}</p>
+                  <div className={styles.container}>
+                    <div className={styles.server}>
+                      <div className={styles.subTitle}>{item?.channelInfo?.name || '--'}</div>
+                      <div style={{width: '100%'}}>
+                        {item.channelInfo?.addresses.map((i: any, index: number) => (
+                          <p key={i.address + `_address${index}`}>
+                            <Badge color={i.health === -1 ? 'red' : 'green'} text={i.address}/>
+                          </p>
+                        ))}
                       </div>
                     </div>
+                    <div className={styles.procotol}>
+                      <div className={styles.subTitle}>{item?.protocolDetail?.name || '--'}</div>
+                      <p>{item.protocolDetail?.description || '--'}</p>
+                    </div>
                   </div>
                 </div>
-              </TableCard>
-            </div>
-          ))
-        : null}
+              </div>
+            </TableCard>
+          </div>
+        ))
+      ) : (
+        <Empty description={<span>{emptyDescription}</span>}/>
+      )}
     </div>
   );
 };

+ 8 - 8
src/pages/media/Device/Save/SaveProduct.tsx

@@ -1,7 +1,7 @@
-import { useEffect, useState } from 'react';
-import { service } from '../index';
-import { useRequest } from 'umi';
-import { Form, Input, message, Modal } from 'antd';
+import {useEffect, useState} from 'react';
+import {service} from '../index';
+import {useRequest} from 'umi';
+import {Form, Input, message, Modal} from 'antd';
 import ProviderItem from './ProviderSelect';
 
 interface SaveProps {
@@ -24,10 +24,9 @@ export default (props: SaveProps) => {
   useEffect(() => {
     if (visible) {
       getProviderList({
-        terms: [
-          { column: 'provider', value: props.type },
-          { column: 'state', value: 'enabled' },
-        ],
+        sorts: [{name: 'createTime', value: 'desc'}],
+        terms: [{column: 'provider', value: props.type}],
+        pageSize: 100,
       });
     }
   }, [visible]);
@@ -107,6 +106,7 @@ export default (props: SaveProps) => {
         >
           <ProviderItem
             options={providerList}
+            type={props.type}
             onSelect={(_, rowData) => {
               form.setFieldsValue({
                 accessName: rowData.name,

+ 15 - 10
src/pages/media/Device/Save/index.tsx

@@ -1,12 +1,12 @@
-import { useCallback, useEffect, useState } from 'react';
-import { Button, Col, Form, Input, message, Modal, Radio, Row, Select, Tooltip } from 'antd';
-import { useIntl } from 'umi';
-import { RadioCard, UploadImage } from '@/components';
-import { PlusOutlined } from '@ant-design/icons';
-import { service } from '../index';
+import {useCallback, useEffect, useState} from 'react';
+import {Button, Col, Form, Input, message, Modal, Radio, Row, Select, Tooltip} from 'antd';
+import {useIntl} from 'umi';
+import {RadioCard, UploadImage} from '@/components';
+import {PlusOutlined} from '@ant-design/icons';
+import {service} from '../index';
 import SaveProductModal from './SaveProduct';
-import type { DeviceItem } from '../typings';
-import { getButtonPermission } from '@/utils/menu';
+import type {DeviceItem} from '../typings';
+import {getButtonPermission} from '@/utils/menu';
 
 interface SaveProps {
   visible: boolean;
@@ -326,9 +326,14 @@ export default (props: SaveProps) => {
             <Col span={24}>
               <Form.Item label={'说明'} name={'description'}>
                 <Input.TextArea
-                  placeholder={intlFormat('pages.form.tip.input', '请输入')}
+                  placeholder={intlFormat(
+                    'pages.form.tip.input.props',
+                    '请输入',
+                    'pages.table.describe',
+                    '说明',
+                  )}
                   rows={4}
-                  style={{ width: '100%' }}
+                  style={{width: '100%'}}
                   maxLength={200}
                   showCount={true}
                 />

+ 10 - 15
src/pages/media/Device/index.tsx

@@ -1,8 +1,8 @@
 // 视频设备列表
-import { PageContainer } from '@ant-design/pro-layout';
-import { useRef, useState } from 'react';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Button, message, Tooltip } from 'antd';
+import {PageContainer} from '@ant-design/pro-layout';
+import {useRef, useState} from 'react';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
+import {Button, message, Tooltip} from 'antd';
 import {
   DeleteOutlined,
   EditOutlined,
@@ -11,24 +11,19 @@ import {
   PlusOutlined,
   SyncOutlined,
 } from '@ant-design/icons';
-import type { DeviceItem } from '@/pages/media/Device/typings';
-import { useHistory, useIntl } from 'umi';
-import { BadgeStatus, ProTableCard, PermissionButton } from '@/components';
-import { StatusColorEnum } from '@/components/BadgeStatus';
+import type {DeviceItem} from '@/pages/media/Device/typings';
+import {useHistory, useIntl} from 'umi';
+import {BadgeStatus, PermissionButton, ProTableCard} from '@/components';
+import {StatusColorEnum} from '@/components/BadgeStatus';
 import SearchComponent from '@/components/SearchComponent';
 import MediaDevice from '@/components/ProTableCard/CardItems/mediaDevice';
-import {
-  // getButtonPermission,
-  getMenuPathByCode,
-  getMenuPathByParams,
-  MENUS_CODE,
-} from '@/utils/menu';
+import {getMenuPathByCode, getMenuPathByParams, MENUS_CODE,} from '@/utils/menu';
 import Service from './service';
 import Save from './Save';
 
 export const service = new Service('media/device');
 
-const providerType = {
+export const providerType = {
   'gb28181-2016': 'GB/T28181',
   'fixed-media': '固定地址',
 };

+ 12 - 12
src/pages/system/Menu/Detail/buttons.tsx

@@ -1,14 +1,14 @@
-import { Button, Form, Input, message, Modal, Tooltip } from 'antd';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { useCallback, useEffect, useState } from 'react';
-import { service } from '@/pages/system/Menu';
-import type { ProColumns } from '@jetlinks/pro-table';
+import {Button, Form, Input, message, Modal, Tooltip} from 'antd';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import {useCallback, useEffect, useState} from 'react';
+import {service} from '@/pages/system/Menu';
+import type {ProColumns} from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { DeleteOutlined, EditOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons';
-import type { MenuButtonInfo, MenuItem } from '@/pages/system/Menu/typing';
+import {DeleteOutlined, EditOutlined, PlusOutlined, SearchOutlined} from '@ant-design/icons';
+import type {MenuButtonInfo, MenuItem} from '@/pages/system/Menu/typing';
 import Permission from '@/pages/system/Menu/components/permission';
-import { useRequest } from '@@/plugin-request/request';
-import { PermissionButton } from '@/components';
+import {useRequest} from '@@/plugin-request/request';
+import {PermissionButton} from '@/components';
 
 type ButtonsProps = {
   data: MenuItem;
@@ -153,7 +153,7 @@ export default (props: ButtonsProps) => {
     {
       title: intl.formatMessage({
         id: 'page.system.menu.describe',
-        defaultMessage: '备注说明',
+        defaultMessage: '说明',
       }),
       dataIndex: 'description',
       // render: (_, row) => () => {
@@ -348,10 +348,10 @@ export default (props: ButtonsProps) => {
             name="description"
             label={intl.formatMessage({
               id: 'pages.table.describe',
-              defaultMessage: '描述',
+              defaultMessage: '说明',
             })}
           >
-            <Input.TextArea disabled={disabled} placeholder={'请输入描述'} />
+            <Input.TextArea disabled={disabled} placeholder={'请输入说明'}/>
           </Form.Item>
         </Form>
       </Modal>

+ 11 - 23
src/pages/system/Menu/Detail/edit.tsx

@@ -1,28 +1,16 @@
-import {
-  Card,
-  Col,
-  Form,
-  Input,
-  InputNumber,
-  message,
-  Radio,
-  Row,
-  Select,
-  Tooltip,
-  TreeSelect,
-} from 'antd';
+import {Card, Col, Form, Input, InputNumber, message, Radio, Row, Select, Tooltip, TreeSelect,} from 'antd';
 import Permission from '@/pages/system/Menu/components/permission';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { useEffect, useState } from 'react';
-import { service } from '@/pages/system/Menu';
-import { useHistory, useRequest } from 'umi';
-import type { MenuItem } from '@/pages/system/Menu/typing';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import {useEffect, useState} from 'react';
+import {service} from '@/pages/system/Menu';
+import {useHistory, useRequest} from 'umi';
+import type {MenuItem} from '@/pages/system/Menu/typing';
 // import { debounce } from 'lodash';
 import Title from '../components/Title';
 import Icons from '../components/Icons';
-import { QuestionCircleFilled } from '@ant-design/icons';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { PermissionButton } from '@/components';
+import {QuestionCircleFilled} from '@ant-design/icons';
+import {getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import {PermissionButton} from '@/components';
 
 type EditProps = {
   data: MenuItem;
@@ -36,7 +24,7 @@ export default (props: EditProps) => {
   const [show] = useState(true);
   const [accessSupport, setAccessSupport] = useState('unsupported');
   const history = useHistory();
-  const { permission } = PermissionButton.usePermission('system/Menu');
+  const {getOtherPermission} = PermissionButton.usePermission('system/Menu');
 
   const [form] = Form.useForm();
 
@@ -328,7 +316,7 @@ export default (props: EditProps) => {
                 saveData();
               }
             }}
-            isPermission={disabled ? permission.update : permission.add}
+            isPermission={getOtherPermission(['add', 'update'])}
           >
             {intl.formatMessage({
               id: `pages.data.option.${disabled ? 'edit' : 'save'}`,

+ 15 - 19
src/pages/system/Menu/index.tsx

@@ -1,25 +1,20 @@
 // 菜单管理
-import { PageContainer } from '@ant-design/pro-layout';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import {PageContainer} from '@ant-design/pro-layout';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { useRef, useState } from 'react';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { Button, message, Tooltip } from 'antd';
-import {
-  DeleteOutlined,
-  PlusCircleOutlined,
-  PlusOutlined,
-  SearchOutlined,
-} from '@ant-design/icons';
-import { observer } from '@formily/react';
-import { model } from '@formily/reactive';
-import { useHistory } from 'umi';
+import {useRef, useState} from 'react';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import {Button, message, Tooltip} from 'antd';
+import {DeleteOutlined, PlusCircleOutlined, PlusOutlined, SearchOutlined,} from '@ant-design/icons';
+import {observer} from '@formily/react';
+import {model} from '@formily/reactive';
+import {useHistory} from 'umi';
 import SearchComponent from '@/components/SearchComponent';
 import Service from './service';
-import type { MenuItem } from './typing';
+import type {MenuItem} from './typing';
 import moment from 'moment';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { PermissionButton } from '@/components';
+import {getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import {PermissionButton} from '@/components';
 
 export const service = new Service('menu');
 
@@ -107,7 +102,7 @@ export default observer(() => {
     {
       title: intl.formatMessage({
         id: 'page.system.menu.describe',
-        defaultMessage: '备注说明',
+        defaultMessage: '说明',
       }),
       width: 200,
       dataIndex: 'describe',
@@ -174,7 +169,8 @@ export default observer(() => {
         <PermissionButton
           key="delete"
           type="link"
-          style={{ padding: 0 }}
+          style={{padding: 0}}
+          isPermission={permission.delete}
           popConfirm={{
             title: intl.formatMessage({
               id: 'page.system.menu.table.delete',

+ 50 - 36
src/pages/system/Permission/index.tsx

@@ -1,26 +1,20 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import React, { useRef, useState } from 'react';
-import {
-  CloseCircleOutlined,
-  DeleteOutlined,
-  EditOutlined,
-  PlayCircleOutlined,
-  PlusOutlined,
-} from '@ant-design/icons';
-import { Badge, Button, Dropdown, Menu, message, Popconfirm, Space, Upload } from 'antd';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import {PageContainer} from '@ant-design/pro-layout';
+import React, {useRef, useState} from 'react';
+import {CloseCircleOutlined, DeleteOutlined, EditOutlined, PlayCircleOutlined, PlusOutlined,} from '@ant-design/icons';
+import {Badge, Button, Dropdown, Menu, message, Popconfirm, Space, Tooltip, Upload} from 'antd';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import type { PermissionItem } from '@/pages/system/Permission/typings';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import type {PermissionItem} from '@/pages/system/Permission/typings';
 import Service from '@/pages/system/Permission/service';
-import { observer } from '@formily/react';
+import {observer} from '@formily/react';
 import SearchComponent from '@/components/SearchComponent';
 import Save from './Save';
 import SystemConst from '@/utils/const';
-import { downloadObject } from '@/utils/util';
+import {downloadObject} from '@/utils/util';
 import Token from '@/utils/token';
-import { getButtonPermission } from '@/utils/menu';
-import { PermissionButton } from '@/components';
+import {getButtonPermission} from '@/utils/menu';
+import {PermissionButton} from '@/components';
 
 export const service = new Service('permission');
 const Permission: React.FC = observer(() => {
@@ -49,24 +43,34 @@ const Permission: React.FC = observer(() => {
           showUploadList={false}
           accept=".json"
           beforeUpload={(file) => {
-            const reader = new FileReader();
-            reader.readAsText(file);
-            reader.onload = (result: any) => {
-              try {
-                const data = JSON.parse(result.target.result);
-                service.batchAdd(data).subscribe((resp) => {
-                  if (resp.status === 200) {
-                    message.success('导入成功');
-                    actionRef.current?.reload();
-                  }
-                });
-              } catch (error) {
-                message.error('导入失败,请重试!');
-              }
-            };
+            if (file.type === 'application/json') {
+              const reader = new FileReader();
+              reader.readAsText(file);
+              reader.onload = (result: any) => {
+                try {
+                  const data = JSON.parse(result.target.result);
+                  service.batchAdd(data).subscribe((resp) => {
+                    if (resp.status === 200) {
+                      message.success('导入成功');
+                      actionRef.current?.reload();
+                    }
+                  });
+                } catch (error) {
+                  message.error('导入失败,请重试!');
+                }
+              };
+            } else {
+              message.error('请上传json格式');
+            }
           }}
         >
-          <Button disabled={getButtonPermission('system/Permission', ['import'])}>导入</Button>
+          <Tooltip
+            title={
+              getButtonPermission('system/Permission', ['import']) ? '暂无权限,请联系管理员' : ''
+            }
+          >
+            <Button disabled={getButtonPermission('system/Permission', ['import'])}>导入</Button>
+          </Tooltip>
         </Upload>
       </Menu.Item>
       <Menu.Item key="export">
@@ -74,7 +78,7 @@ const Permission: React.FC = observer(() => {
           disabled={getButtonPermission('system/Permission', ['export'])}
           title={'确认导出?'}
           onConfirm={() => {
-            service.getPermission({ ...param, paging: false }).subscribe((resp) => {
+            service.getPermission({...param, paging: false}).subscribe((resp) => {
               if (resp.status === 200) {
                 downloadObject(resp.result, '权限数据');
                 message.success('导出成功');
@@ -84,7 +88,13 @@ const Permission: React.FC = observer(() => {
             });
           }}
         >
-          <Button disabled={getButtonPermission('system/Permission', ['export'])}>导出</Button>
+          <Tooltip
+            title={
+              getButtonPermission('system/Permission', ['export']) ? '暂无权限,请联系管理员' : ''
+            }
+          >
+            <Button disabled={getButtonPermission('system/Permission', ['export'])}>导出</Button>
+          </Tooltip>
         </Popconfirm>
       </Menu.Item>
     </Menu>
@@ -201,8 +211,12 @@ const Permission: React.FC = observer(() => {
         <PermissionButton
           type={'link'}
           key={'delete'}
-          style={{ padding: 0 }}
+          style={{padding: 0}}
+          disabled={!!record.status}
           isPermission={permission.delete}
+          tooltip={{
+            title: !!record.status ? '请先禁用,再删除' : '删除',
+          }}
           popConfirm={{
             title: '确认删除',
             onConfirm: async () => {

+ 14 - 14
src/pages/system/Role/index.tsx

@@ -1,18 +1,18 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import React, { useEffect, useRef } from 'react';
-import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
-import { message } from 'antd';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import {PageContainer} from '@ant-design/pro-layout';
+import React, {useEffect, useRef} from 'react';
+import {DeleteOutlined, EditOutlined} from '@ant-design/icons';
+import {message} from 'antd';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
 import BaseCrud from '@/components/BaseCrud';
 import Service from './service';
-import { useIntl } from '@@/plugin-locale/localeExports';
-import { observer } from '@formily/react';
-import { history, useLocation } from 'umi';
-import { Store } from 'jetlinks-store';
+import {useIntl} from '@@/plugin-locale/localeExports';
+import {observer} from '@formily/react';
+import {history, useLocation} from 'umi';
+import {Store} from 'jetlinks-store';
 import SystemConst from '@/utils/const';
-import { CurdModel } from '@/components/BaseCrud/model';
-import { getButtonPermission, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { PermissionButton } from '@/components';
+import {CurdModel} from '@/components/BaseCrud/model';
+import {getButtonPermission, getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import {PermissionButton} from '@/components';
 
 export const service = new Service('role');
 
@@ -20,7 +20,7 @@ const Role: React.FC = observer(() => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
   const permissionCode = 'system/Role';
-  const { permission } = PermissionButton.usePermission(permissionCode);
+  const {permission} = PermissionButton.usePermission(permissionCode);
 
   const columns: ProColumns<RoleItem>[] = [
     // {
@@ -175,7 +175,7 @@ const Role: React.FC = observer(() => {
         'x-component': 'Input.TextArea',
         'x-component-props': {
           checkStrength: true,
-          placeholder: '请输入描述',
+          placeholder: '请输入说明',
         },
         'x-decorator-props': {},
         name: 'password',

+ 11 - 11
src/pages/system/Tenant/index.tsx

@@ -1,19 +1,19 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import type { TenantDetail, TenantItem } from '@/pages/system/Tenant/typings';
+import {PageContainer} from '@ant-design/pro-layout';
+import type {ActionType, ProColumns} from '@jetlinks/pro-table';
+import type {TenantDetail, TenantItem} from '@/pages/system/Tenant/typings';
 import BaseCrud from '@/components/BaseCrud';
-import { useRef } from 'react';
-import { Avatar, Drawer, Tooltip } from 'antd';
+import {useRef} from 'react';
+import {Avatar, Drawer, Tooltip} from 'antd';
 import Service from '@/pages/system/Tenant/service';
-import { EyeOutlined, KeyOutlined } from '@ant-design/icons';
-import { useIntl } from '@@/plugin-locale/localeExports';
+import {EyeOutlined, KeyOutlined} from '@ant-design/icons';
+import {useIntl} from '@@/plugin-locale/localeExports';
 import moment from 'moment';
-import { Link } from 'umi';
+import {Link} from 'umi';
 import TenantModel from '@/pages/system/Tenant/model';
-import type { ISchema } from '@formily/json-schema';
+import type {ISchema} from '@formily/json-schema';
 import autzModel from '@/components/Authorization/autz';
 import Authorization from '@/components/Authorization';
-import { observer } from '@formily/react';
+import {observer} from '@formily/react';
 
 export const service = new Service('tenant');
 
@@ -215,7 +215,7 @@ const Tenant = observer(() => {
       },
       description: {
         type: 'string',
-        title: '备注',
+        title: '说明',
         required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Input.TextArea',

+ 1 - 1
src/utils/menu/router.ts

@@ -121,7 +121,7 @@ export enum BUTTON_PERMISSION_ENUM {
   'sync' = 'sync',
   'channel' = 'channel',
   'debug' = 'debug',
-  'log' = 'log'
+  'log' = 'log',
 }
 
 // 调试按钮、通知记录、批量导出、批量导入、选择通道、推送、分配资产、绑定用户对应的ID是啥