Przeglądaj źródła

fix(product): Product Detail

Lind 4 lat temu
rodzic
commit
cdfb7d589f

+ 2 - 1
package.json

@@ -84,6 +84,7 @@
     "react-dev-inspector": "^1.1.1",
     "react-dom": "^17.0.0",
     "react-helmet-async": "^1.0.4",
+    "reconnecting-websocket": "^4.4.0",
     "rxjs": "^7.2.0",
     "rxjs-websockets": "8",
     "umi": "^3.5.0",
@@ -121,7 +122,7 @@
     "prettier": "^2.3.2",
     "puppeteer-core": "^8.0.0",
     "stylelint": "^13.0.0",
-    "typescript": "^4.2.2"
+    "typescript": "^4.4.4"
   },
   "engines": {
     "node": ">=10.0.0"

+ 14 - 0
src/app.tsx

@@ -9,6 +9,8 @@ import { BookOutlined, LinkOutlined } from '@ant-design/icons';
 import Service from '@/pages/user/Login/service';
 import Token from '@/utils/token';
 import type { RequestOptionsInit } from 'umi-request';
+import ReconnectingWebSocket from 'reconnecting-websocket';
+import SystemConst from '@/utils/const';
 
 const isDev = process.env.NODE_ENV === 'development';
 const loginPath = '/user/login';
@@ -44,6 +46,18 @@ export async function getInitialState(): Promise<{
       settings: {},
     };
   }
+  // 链接websocket
+  const url = `${document.location.protocol.replace('http', 'ws')}//${document.location.host}/${
+    SystemConst.API_BASE
+  }/messaging/${Token.get()}?:X_Access_Token=${Token.get()}`;
+
+  const ws = new ReconnectingWebSocket(url);
+
+  ws.send('sss');
+  ws.onerror = () => {
+    console.log('链接错误。ws');
+  };
+
   return {
     fetchUserInfo,
     settings: {},

+ 13 - 8
src/components/Authorization/index.tsx

@@ -5,12 +5,12 @@ import { useCallback, useEffect, useMemo } from 'react';
 import encodeQuery from '@/utils/encodeQuery';
 import { map, scan, takeLast } from 'rxjs/operators';
 import { toArray } from 'rxjs';
-import db from '@/db';
 import Service from '@/components/Authorization/service';
 import styles from './index.less';
 import _ from 'lodash';
 import { AuthorizationModel } from '@/components/Authorization/autz';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import DB from '@/db';
 
 const service = new Service();
 
@@ -43,12 +43,12 @@ const Authorization = observer((props: AuthorizationProps) => {
     [target.id],
   );
 
-  const queryDB = () => db.table(tableName).reverse().sortBy('name');
+  const queryDB = () => DB.getDB().table(tableName).reverse().sortBy('name');
 
   const insertDB = useCallback(
     async (permission: PermissionItem[]) => {
-      db.table(tableName).clear();
-      db.table(tableName).bulkAdd(permission);
+      DB.getDB().table(tableName).clear();
+      DB.getDB().table(tableName).bulkAdd(permission);
       AuthorizationModel.data = await queryDB();
     },
     [AuthorizationModel.data],
@@ -57,7 +57,7 @@ const Authorization = observer((props: AuthorizationProps) => {
   const searchPermission = async (name: string, type: string) => {
     AuthorizationModel.filterParam.name = name;
     AuthorizationModel.filterParam.type = type;
-    AuthorizationModel.data = await db
+    AuthorizationModel.data = await DB.getDB()
       .table(tableName)
       .where('name')
       .startsWith(name)
@@ -131,11 +131,16 @@ const Authorization = observer((props: AuthorizationProps) => {
   }, []);
 
   useEffect(() => {
-    initPermission();
+    DB.updateSchema({
+      permission: 'id,name,status,describe,type',
+    }).then(() => {
+      initPermission();
+    });
 
     return () => {
-      db.table(tableName).clear();
+      DB.getDB().table(tableName).clear();
       AuthorizationModel.spinning = true;
+      DB.updateSchema({ permission: null });
     };
   }, [target.id]);
 
@@ -199,7 +204,7 @@ const Authorization = observer((props: AuthorizationProps) => {
         <Checkbox
           checked={AuthorizationModel.checkAll}
           onChange={async (e) => {
-            const permissionDB: PermissionItem[] = await db
+            const permissionDB: PermissionItem[] = await DB.getDB()
               .table(tableName)
               .reverse()
               .sortBy('name');

+ 33 - 5
src/db.ts

@@ -1,9 +1,37 @@
 import Dexie from 'dexie';
+import SystemConst from '@/utils/const';
 
-const db = new Dexie('permission');
+class DexieDB {
+  public db: Dexie;
 
-db.version(3).stores({
-  permission: 'id,name,status,describe,type',
-});
+  constructor() {
+    this.db = new Dexie(SystemConst.API_BASE);
+  }
 
-export default db;
+  getDB() {
+    if (this.db && this.db?.verno === 0) {
+      // 考虑优化
+      // 获取不到真实的数据库版本
+      // 所以当获取到的数据库版本号为0则删除数据库重新初始化
+      this.db.delete();
+      this.db = new Dexie(SystemConst.API_BASE);
+      this.db.version(1);
+      return this.db;
+    }
+    return this.db;
+  }
+
+  updateSchema = async (extendedSchema: Record<string, string | null>) => {
+    // 打开后才能获取正确的版本号
+    // console.log(database)
+    this.getDB().close();
+    // 关闭后才可以更改表结构
+    this.getDB()
+      .version(this.db.verno + 1)
+      .stores(extendedSchema);
+    return this.getDB().open();
+  };
+}
+
+const DB = new DexieDB();
+export default DB;

+ 9 - 4
src/hooks/websocket/useWebSocket.ts

@@ -54,11 +54,13 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
     } else {
       if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
 
-      if (websocketRef.current) {
-        websocketRef.current.close();
-      }
+      // if (websocketRef.current) {
+      //   // 此处应考虑状态。
+      //   websocketRef.current.close();
+      // }
 
       try {
+        console.log(websocketRef.current, 'current');
         websocketRef.current = new WebSocket(socketUrl);
 
         websocketRef.current.onerror = (event) => {
@@ -118,7 +120,10 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
       ws.send(message);
     } else {
       connectWs();
-      ws.send(message);
+      // todo 考虑重写
+      setTimeout(() => {
+        ws.send(message);
+      }, 3000);
       // throw new Error('WebSocket disconnected');
     }
   });

+ 34 - 0
src/pages/device/Product/Detail/Alarm/Record/index.tsx

@@ -0,0 +1,34 @@
+import type { AlarmRecord } from '@/pages/device/Product/typings';
+import type { ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+
+const Record = () => {
+  const columns: ProColumns<AlarmRecord>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      title: '设备ID',
+      dataIndex: 'deviceId',
+    },
+    {
+      title: '设备名称',
+      dataIndex: 'deviceName',
+    },
+    {
+      title: '告警时间',
+      dataIndex: 'alarmTime',
+      defaultSortOrder: 'descend',
+      sorter: true,
+    },
+    {
+      title: '处理状态',
+      dataIndex: 'state',
+    },
+  ];
+
+  return <ProTable columns={columns} rowKey="id" search={false} />;
+};
+export default Record;

+ 44 - 0
src/pages/device/Product/Detail/Alarm/Setting/index.tsx

@@ -0,0 +1,44 @@
+import type { ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import type { AlarmSetting } from '@/pages/device/Product/typings';
+import { Button, Space } from 'antd';
+
+const Setting = () => {
+  const columns: ProColumns<AlarmSetting>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      title: '标识',
+      dataIndex: 'id',
+    },
+    {
+      title: '名称',
+      dataIndex: 'name',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+    },
+    {
+      title: '状态',
+      dataIndex: 'state.text',
+    },
+  ];
+
+  return (
+    <ProTable
+      tableAlertOptionRender={() => (
+        <Space size={16}>
+          <Button onClick={() => {}}>新增告警</Button>
+        </Space>
+      )}
+      columns={columns}
+      rowKey="id"
+      search={false}
+    />
+  );
+};
+export default Setting;

+ 17 - 0
src/pages/device/Product/Detail/Alarm/index.tsx

@@ -0,0 +1,17 @@
+import { Tabs } from 'antd';
+import Setting from '@/pages/device/Product/Detail/Alarm/Setting';
+import Record from '@/pages/device/Product/Detail/Alarm/Record';
+
+const Alarm = () => {
+  return (
+    <Tabs>
+      <Tabs.TabPane key="setting" tab="告警设置">
+        <Setting />
+      </Tabs.TabPane>
+      <Tabs.TabPane key="record" tab="告警记录">
+        <Record />
+      </Tabs.TabPane>
+    </Tabs>
+  );
+};
+export default Alarm;

+ 17 - 14
src/pages/device/Product/Detail/BaseInfo/index.tsx

@@ -1,18 +1,20 @@
-import { createSchemaField, observer } from '@formily/react';
+import { createSchemaField } from '@formily/react';
 import { productModel, service } from '@/pages/device/Product';
 import { Form, FormItem, FormGrid, Password, FormLayout, PreviewText, Input } from '@formily/antd';
 import { createForm } from '@formily/core';
 import { Card, Empty } from 'antd';
 import type { ISchema } from '@formily/json-schema';
-import { SetStateAction, useEffect, useState } from 'react';
+import type { SetStateAction } from 'react';
+import { useEffect, useState } from 'react';
 import type { ConfigMetadata, ConfigProperty } from '@/pages/device/Product/typings';
+import { useParams } from 'umi';
 
 const componentMap = {
   string: 'Input',
   password: 'Password',
 };
-const BaseInfo = observer(() => {
-  const id = productModel.current?.id;
+const BaseInfo = () => {
+  const param = useParams<{ id: string }>();
   const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
   const [state, setState] = useState<boolean>(false);
 
@@ -23,22 +25,22 @@ const BaseInfo = observer(() => {
   });
 
   useEffect(() => {
-    if (id) {
-      service.getConfigMetadata(id).then((config: { result: SetStateAction<ConfigMetadata[]> }) => {
-        setMetadata(config.result);
-      });
+    if (param.id) {
+      service
+        .getConfigMetadata(param.id)
+        .then((config: { result: SetStateAction<ConfigMetadata[]> }) => {
+          setMetadata(config.result);
+        });
     }
-
-    return () => {};
-  }, [id]);
+  }, [param.id]);
 
   const SchemaField = createSchemaField({
     components: {
-      FormItem,
-      Input,
       Password,
       FormGrid,
       PreviewText,
+      FormItem,
+      Input,
     },
   });
 
@@ -78,6 +80,7 @@ const BaseInfo = observer(() => {
 
         return (
           <Card
+            key={item.name}
             title={item.name}
             extra={<a onClick={() => setState(!state)}>{state ? '编辑' : '保存'}</a>}
           >
@@ -97,5 +100,5 @@ const BaseInfo = observer(() => {
   };
 
   return <>{renderConfigCard()}</>;
-});
+};
 export default BaseInfo;

+ 33 - 0
src/pages/device/Product/Detail/Metadata/Event/index.tsx

@@ -0,0 +1,33 @@
+import type { EventMetadata } from '@/pages/device/Product/typings';
+import type { ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+
+const Event = () => {
+  const columns: ProColumns<EventMetadata>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      title: '标识',
+      dataIndex: 'id',
+    },
+    {
+      title: '名称',
+      dataIndex: 'name',
+    },
+    {
+      title: '事件级别',
+      dataIndex: 'expands.level',
+    },
+    {
+      title: '说明',
+      dataIndex: 'description',
+      ellipsis: true,
+    },
+  ];
+
+  return <ProTable columns={columns} rowKey="id" search={false} />;
+};
+export default Event;

+ 37 - 0
src/pages/device/Product/Detail/Metadata/Function/index.tsx

@@ -0,0 +1,37 @@
+import ProTable, { ProColumns } from '@jetlinks/pro-table';
+import { FunctionMetadata } from '@/pages/device/Product/typings';
+
+const Function = () => {
+  const columns: ProColumns<FunctionMetadata>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      title: '标识',
+      dataIndex: 'id',
+    },
+    {
+      title: '名称',
+      dataIndex: 'name',
+    },
+    {
+      title: '是否异步',
+      dataIndex: 'async',
+    },
+    {
+      title: '是否只读',
+      dataIndex: 'expands.readOnly',
+      render: (text) => (text ? '是' : '否'),
+    },
+    {
+      title: '说明',
+      dataIndex: 'description',
+      ellipsis: true,
+    },
+  ];
+
+  return <ProTable columns={columns} rowKey="id" search={false} />;
+};
+export default Function;

+ 38 - 0
src/pages/device/Product/Detail/Metadata/Property/index.tsx

@@ -0,0 +1,38 @@
+import type { ProColumns } from '@jetlinks/pro-table';
+import type { PropertyMetadata } from '@/pages/device/Product/typings';
+import ProTable from '@jetlinks/pro-table';
+
+const Property = () => {
+  const columns: ProColumns<PropertyMetadata>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      title: '标识',
+      dataIndex: 'id',
+    },
+    {
+      title: '名称',
+      dataIndex: 'name',
+    },
+    {
+      title: '数据类型',
+      dataIndex: 'dataType',
+    },
+    {
+      title: '是否只读',
+      dataIndex: 'expands.readOnly',
+      render: (text) => (text === 'true' || text === true ? '是' : '否'),
+    },
+    {
+      title: '说明',
+      dataIndex: 'description',
+      ellipsis: true,
+    },
+  ];
+
+  return <ProTable columns={columns} rowKey="id" search={false} />;
+};
+export default Property;

+ 38 - 0
src/pages/device/Product/Detail/Metadata/Tag/index.tsx

@@ -0,0 +1,38 @@
+import type { TagMetadata } from '@/pages/device/Product/typings';
+import type { ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+
+const Tag = () => {
+  const columns: ProColumns<TagMetadata>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      title: '标识',
+      dataIndex: 'id',
+    },
+    {
+      title: '名称',
+      dataIndex: 'name',
+    },
+    {
+      title: '数据类型',
+      dataIndex: 'valueType',
+    },
+    {
+      title: '是否只读',
+      dataIndex: 'expands.readOnly',
+      render: (text) => (text === 'true' || text === true ? '是' : '否'),
+    },
+    {
+      title: '说明',
+      dataIndex: 'description',
+      ellipsis: true,
+    },
+  ];
+
+  return <ProTable columns={columns} rowKey="id" search={false} />;
+};
+export default Tag;

+ 33 - 0
src/pages/device/Product/Detail/Metadata/index.tsx

@@ -0,0 +1,33 @@
+import { observer } from '@formily/react';
+import { Button, Space, Tabs } from 'antd';
+import Property from '@/pages/device/Product/Detail/Metadata/Property';
+import Function from '@/pages/device/Product/Detail/Metadata/Function';
+import Event from '@/pages/device/Product/Detail/Metadata/Event';
+import Tag from '@/pages/device/Product/Detail/Metadata/Tag';
+
+const Metadata = observer(() => {
+  return (
+    <Tabs
+      tabBarExtraContent={
+        <Space>
+          <Button>快速导入</Button>
+          <Button>物模型TSL</Button>
+        </Space>
+      }
+    >
+      <Tabs.TabPane tab="属性定义" key="property">
+        <Property />
+      </Tabs.TabPane>
+      <Tabs.TabPane tab="功能定义" key="func">
+        <Function />
+      </Tabs.TabPane>
+      <Tabs.TabPane tab="事件定义" key="event">
+        <Event />
+      </Tabs.TabPane>
+      <Tabs.TabPane tab="标签定义" key="tag">
+        <Tag />
+      </Tabs.TabPane>
+    </Tabs>
+  );
+});
+export default Metadata;

+ 66 - 11
src/pages/device/Product/Detail/index.tsx

@@ -1,17 +1,71 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { history } from 'umi';
-import { Button, Card, Descriptions, Space, Tabs } from 'antd';
+import { history, useParams } from 'umi';
+import { Button, Card, Descriptions, message, Space, Tabs } from 'antd';
 import BaseInfo from '@/pages/device/Product/Detail/BaseInfo';
 import { observer } from '@formily/react';
-import { productModel, statusMap } from '@/pages/device/Product';
+import { productModel, service, statusMap } from '@/pages/device/Product';
 import { useEffect } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import Metadata from '@/pages/device/Product/Detail/Metadata';
+import Alarm from '@/pages/device/Product/Detail/Alarm';
+import type { DeviceMetadata } from '@/pages/device/Product/typings';
+import DB from '@/db';
 
 const ProductDetail = observer(() => {
   const intl = useIntl();
+  const param = useParams<{ id: string }>();
+
   useEffect(() => {
-    if (!productModel.current) history.goBack();
-  }, []);
+    if (!productModel.current) {
+      history.goBack();
+    } else {
+      service.getProductDetail(param.id).subscribe((data) => {
+        // 存储到数据库
+        // events  functions  properties  tags
+        // 数据库存储 按设备名称-物模型类别存储  如:yanshi-tags
+        const metadata: DeviceMetadata = JSON.parse(data.metadata);
+
+        DB.updateSchema({
+          [`${param.id}-events`]: 'id,name',
+          [`${param.id}-property`]: 'id,name',
+          [`${param.id}-function`]: 'id,name',
+          [`${param.id}-tag`]: 'id,name',
+        })
+          .then(() => {
+            /// 应该先判断是否存在数据
+            const EventTable = DB.getDB().table(`${param.id}-events`);
+            EventTable.clear().then(() => {
+              EventTable.bulkAdd(metadata.events || []);
+            });
+            const PropertyTable = DB.getDB().table(`${param.id}-property`);
+            PropertyTable.clear().then(() => {
+              PropertyTable.bulkAdd(metadata.properties || []);
+            });
+            const FunctionTable = DB.getDB().table(`${param.id}-function`);
+            FunctionTable.clear().then(() => {
+              FunctionTable.bulkAdd(metadata.functions || []);
+            });
+            const TagTable = DB.getDB().table(`${param.id}-tag`);
+            TagTable.clear().then(() => {
+              TagTable.bulkAdd(metadata.tags || []);
+            });
+          })
+          .catch((error) => {
+            console.log(error, 'error');
+            message.error(JSON.stringify(error));
+          });
+      });
+    }
+    return () => {
+      // 删除表是把index 设置为空
+      DB.updateSchema({
+        [`${param.id}-events`]: null,
+        [`${param.id}-property`]: null,
+        [`${param.id}-function`]: null,
+        [`${param.id}-tag`]: null,
+      });
+    };
+  }, [param.id]);
   return (
     <PageContainer
       onBack={() => history.goBack()}
@@ -84,14 +138,15 @@ const ProductDetail = observer(() => {
         </Button>,
       ]}
     >
+      {JSON.stringify(productModel.current)}
       <Card>
-        <Tabs defaultActiveKey={'base'}>
+        <Tabs tabPosition="left" defaultActiveKey="base">
           <Tabs.TabPane
             tab={intl.formatMessage({
               id: 'pages.device.productDetail.base',
               defaultMessage: '配置信息',
             })}
-            key={'base'}
+            key="base"
           >
             <BaseInfo />
           </Tabs.TabPane>
@@ -100,18 +155,18 @@ const ProductDetail = observer(() => {
               id: 'pages.device.productDetail.metadata',
               defaultMessage: '物模型',
             })}
-            key={'metadata'}
+            key="metadata"
           >
-            物模型
+            <Metadata />
           </Tabs.TabPane>
           <Tabs.TabPane
             tab={intl.formatMessage({
               id: 'pages.device.productDetail.alarm',
               defaultMessage: '告警设置',
             })}
-            key={'alarm'}
+            key="alarm"
           >
-            告警设置
+            <Alarm />
           </Tabs.TabPane>
         </Tabs>
       </Card>

+ 8 - 2
src/pages/device/Product/service.ts

@@ -2,8 +2,8 @@ import BaseService from '@/utils/BaseService';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import { request } from 'umi';
 import SystemConst from '@/utils/const';
-import { concatMap, from, toArray } from 'rxjs';
-import { map } from 'rxjs/operators';
+import { concatMap, defer, from, toArray } from 'rxjs';
+import { filter, map } from 'rxjs/operators';
 import encodeQuery from '@/utils/encodeQuery';
 import type { Response } from '@/utils/typings';
 import _ from 'lodash';
@@ -29,6 +29,12 @@ class Service extends BaseService<ProductItem> {
 
   public getConfigMetadata = (id: string) =>
     request(`/${SystemConst.API_BASE}/device/product/${id}/config-metadata`, { method: 'GET' });
+
+  public getProductDetail = (id: string) =>
+    defer(() => from(this.detail(id))).pipe(
+      filter((resp) => resp.status === 200),
+      map((resp) => resp.result),
+    );
 }
 
 export default Service;

+ 46 - 0
src/pages/device/Product/typings.d.ts

@@ -1,3 +1,5 @@
+import { BaseItem, State } from '@/utils/typings';
+
 export type ProductItem = {
   id: string;
   name: string;
@@ -98,3 +100,47 @@ type TagMetadata = {
   } & Record<string, any>;
   expands: Record<string, any>;
 };
+
+type AlarmRule = {
+  actions: {
+    configuration: Record<string, unknown>;
+    executor: string;
+  }[];
+  productId: string;
+  productName: string;
+  properties: Record<string, unknown>[];
+  shakeLimit: Record<string, unknown>;
+  triggers: Record<string, unknown>[];
+} & BaseItem;
+
+type AlarmSetting = {
+  state: State;
+  createTime: number;
+  target: string;
+  targetId: string;
+  alarmRule: AlarmRule[];
+} & BaseItem;
+
+type AlarmRecord = {
+  id: string;
+  alarmId: string;
+  alarmName: string;
+  alarmTime: number;
+  description: string;
+  deviceId: string;
+  deviceName: string;
+  productId: string;
+  productName: string;
+  state: string;
+  updateTime: number;
+  alarmData: {
+    alarmId: string;
+    alarmName: string;
+    deviceId: string;
+    deviceName: string;
+    id: string;
+    productId: string;
+    productName: string;
+    timestamp: number;
+  } & Record<string, unknown>;
+};

+ 1 - 16
src/pages/system/Permission/index.tsx

@@ -1,11 +1,6 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import React, { useEffect, useRef } from 'react';
-import {
-  EditOutlined,
-  KeyOutlined,
-  CloseCircleOutlined,
-  PlayCircleOutlined,
-} from '@ant-design/icons';
+import { EditOutlined, CloseCircleOutlined, PlayCircleOutlined } from '@ant-design/icons';
 import { Menu, Tooltip, Popconfirm, message } from 'antd';
 import type { ProColumns, ActionType } from '@jetlinks/pro-table';
 import { useIntl } from '@@/plugin-locale/localeExports';
@@ -171,16 +166,6 @@ const Permission: React.FC = observer(() => {
             <EditOutlined />
           </Tooltip>
         </a>,
-        <a key="authorized" onClick={() => console.log('授权')}>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.authorize',
-              defaultMessage: '授权',
-            })}
-          >
-            <KeyOutlined />
-          </Tooltip>
-        </a>,
         <a key="view">
           <Popconfirm
             title={intl.formatMessage({

+ 4 - 1
src/utils/BaseService.ts

@@ -1,4 +1,3 @@
-// import request from "@/utils/request";
 import Token from '@/utils/token';
 import SystemConst from '@/utils/const';
 import { request } from 'umi';
@@ -39,6 +38,10 @@ class BaseService<T> implements IBaseService<T> {
   update(data: Partial<T>): Promise<any> {
     return request(this.uri, { data, method: 'PATCH' });
   }
+
+  detail(id: string): Promise<any> {
+    return request(`${this.uri}/${id}`, { method: 'GET' });
+  }
 }
 
 export default BaseService;