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

feat(第三方平台): 1.新增Api查看页面;2.完善按钮权限;3.新增swagger页面

xieyonghong 3 лет назад
Родитель
Сommit
8f8fdd0afd

+ 16 - 4
public/icons/iconfont.css

@@ -1,9 +1,9 @@
 @font-face {
   font-family: "iconfont"; /* Project id 3183515 */
-  src: url('iconfont.woff2?t=1649842068082') format('woff2'),
-       url('iconfont.woff?t=1649842068082') format('woff'),
-       url('iconfont.ttf?t=1649842068082') format('truetype'),
-       url('iconfont.svg?t=1649842068082#iconfont') format('svg');
+  src: url('iconfont.woff2?t=1652669655734') format('woff2'),
+  url('iconfont.woff?t=1652669655734') format('woff'),
+  url('iconfont.ttf?t=1652669655734') format('truetype'),
+  url('iconfont.svg?t=1652669655734#iconfont') format('svg');
 }
 
 .iconfont {
@@ -14,6 +14,18 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-chakanAPI:before {
+  content: "\e6af";
+}
+
+.icon-fuquan:before {
+  content: "\e689";
+}
+
+.icon-zhongzhimima:before {
+  content: "\e69b";
+}
+
 .icon-denggao:before {
   content: "\e68a";
 }

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
public/icons/iconfont.js


+ 21 - 0
public/icons/iconfont.json

@@ -6,6 +6,27 @@
   "description": "",
   "glyphs": [
     {
+      "icon_id": "29728797",
+      "name": "查看API",
+      "font_class": "chakanAPI",
+      "unicode": "e6af",
+      "unicode_decimal": 59055
+    },
+    {
+      "icon_id": "29728564",
+      "name": "赋权",
+      "font_class": "fuquan",
+      "unicode": "e689",
+      "unicode_decimal": 59017
+    },
+    {
+      "icon_id": "29728565",
+      "name": "重置密码",
+      "font_class": "zhongzhimima",
+      "unicode": "e69b",
+      "unicode_decimal": 59035
+    },
+    {
       "icon_id": "28953788",
       "name": "等高",
       "font_class": "denggao",

Разница между файлами не показана из-за своего большого размера
+ 6 - 0
public/icons/iconfont.svg


BIN
public/icons/iconfont.ttf


BIN
public/icons/iconfont.woff


BIN
public/icons/iconfont.woff2


+ 27 - 22
src/app.tsx

@@ -140,29 +140,34 @@ export const request: RequestConfig = {
       return;
     }
     if (response.status === 400 || response.status === 500) {
-      response.text().then((resp: string) => {
-        if (resp) {
-          notification.error({
-            key: 'error',
-            message: JSON.parse(resp).message || '服务器内部错误!',
-          });
-        } else {
-          response
-            .json()
-            .then((res: any) => {
-              notification.error({
-                key: 'error',
-                message: `请求错误:${res.message}`,
-              });
-            })
-            .catch(() => {
-              notification.error({
-                key: 'error',
-                message: '系统错误',
-              });
+      // 添加clone() 避免后续其它地方用response.text()时报错
+      response
+        .clone()
+        .text()
+        .then((resp: string) => {
+          if (resp) {
+            notification.error({
+              key: 'error',
+              message: JSON.parse(resp).message || '服务器内部错误!',
             });
-        }
-      });
+          } else {
+            response
+              .clone()
+              .json()
+              .then((res: any) => {
+                notification.error({
+                  key: 'error',
+                  message: `请求错误:${res.message}`,
+                });
+              })
+              .catch(() => {
+                notification.error({
+                  key: 'error',
+                  message: '系统错误',
+                });
+              });
+          }
+        });
       return response;
     }
     if (!response) {

+ 23 - 10
src/pages/device/Instance/index.tsx

@@ -124,19 +124,32 @@ const Instance = () => {
           }.tips`,
           defaultMessage: '确认禁用?',
         }),
-        onConfirm: async () => {
+        onConfirm: () => {
           if (record.state.value !== 'notActive') {
-            await service.undeployDevice(record.id);
+            service.undeployDevice(record.id).then((resp: any) => {
+              if (resp.status === 200) {
+                message.success(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }
+            });
           } else {
-            await service.deployDevice(record.id);
+            service.deployDevice(record.id).then((resp: any) => {
+              if (resp.status === 200) {
+                message.success(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }
+            });
           }
-          message.success(
-            intl.formatMessage({
-              id: 'pages.data.option.success',
-              defaultMessage: '操作成功!',
-            }),
-          );
-          actionRef.current?.reload();
         },
       }}
       isPermission={permission.action}

+ 73 - 0
src/pages/system/Platforms/Api/base.tsx

@@ -0,0 +1,73 @@
+import Tree from '@/pages/system/Platforms/Api/leftTree';
+import Table from '@/pages/system/Platforms/Api/basePage';
+import SwaggerUI from '@/pages/system/Platforms/Api/swagger-ui';
+import { useEffect, useState } from 'react';
+import { service } from '@/pages/system/Platforms';
+import { model } from '@formily/reactive';
+import { observer } from '@formily/react';
+import './index.less';
+
+export const ApiModel = model<{
+  data: any[];
+  baseUrl: string;
+  showTable: boolean;
+  components: any;
+  swagger: any;
+  debugger: any;
+}>({
+  data: [],
+  baseUrl: '',
+  showTable: true,
+  components: {},
+  swagger: {},
+  debugger: {},
+});
+
+interface ApiPageProps {
+  showDebugger?: boolean;
+  isShowGranted?: boolean;
+}
+
+export default observer((props: ApiPageProps) => {
+  const [operations, setOperations] = useState<string[]>([]);
+
+  const initModel = () => {
+    ApiModel.data = [];
+    ApiModel.baseUrl = '';
+    ApiModel.showTable = true;
+    ApiModel.components = {};
+    ApiModel.swagger = {};
+    ApiModel.debugger = {};
+  };
+
+  const getOperations = () => {
+    service.apiOperations().then((resp: any) => {
+      if (resp.status === 200) {
+        setOperations(resp.result);
+      }
+    });
+  };
+
+  useEffect(() => {
+    initModel();
+    getOperations();
+  }, []);
+
+  return (
+    <div className={'platforms-api'}>
+      <div className={'platforms-api-tree'}>
+        <Tree
+          onSelect={(data) => {
+            ApiModel.data = data;
+            ApiModel.showTable = true;
+          }}
+        />
+      </div>
+      {ApiModel.showTable ? (
+        <Table data={ApiModel.data} operations={operations} isShowGranted={props.isShowGranted} />
+      ) : (
+        <SwaggerUI showDebugger={props.showDebugger} />
+      )}
+    </div>
+  );
+});

+ 109 - 34
src/pages/system/Platforms/Api/basePage.tsx

@@ -1,43 +1,104 @@
-import { Button, Table } from 'antd';
+import { Button, message, Table } from 'antd';
 import { useCallback, useEffect, useState } from 'react';
+import { useLocation } from 'umi';
 import { service } from '../index';
+import { ApiModel } from '@/pages/system/Platforms/Api/base';
 
 interface TableProps {
-  parentId: string;
-  onJump: (id: string) => void;
+  data: any;
+  operations: string[];
+  // 是否只暂时已授权的接口
+  isShowGranted?: boolean;
 }
 
 export default (props: TableProps) => {
   const [selectKeys, setSelectKeys] = useState<string[]>([]);
-  const [dataSource, setDataSource] = useState([]);
+  const [dataSource, setDataSource] = useState<any[]>([]);
+  const [loading, setLoading] = useState(false);
+  const location = useLocation();
 
-  const queryData = async (pId: string) => {
-    const resp: any = service.queryRoleList(pId);
-    if (resp.status === 200) {
-      setDataSource(resp.result);
-    }
+  const getApiGrant = useCallback(() => {
+    const param = new URLSearchParams(location.search);
+    const code = param.get('code');
+
+    service.getApiGranted(code!).then((resp: any) => {
+      if (resp.status === 200) {
+        setSelectKeys(resp.result);
+      }
+    });
+  }, [location]);
+
+  const getOperations = async (apiData: any[], operations: string[]) => {
+    // 过滤只能授权的接口,当isShowGranted为true时,过滤为已赋权的接口
+    console.log(
+      apiData.filter((item) => item && item.operationId && operations.includes(item.operationId)),
+    );
+    setDataSource(
+      apiData.filter((item) => item && item.operationId && operations.includes(item.operationId)),
+    );
   };
 
   useEffect(() => {
-    queryData(props.parentId);
-  }, [props.parentId]);
+    if (props.isShowGranted) {
+      if (props.data && selectKeys) {
+        getOperations(props.data, selectKeys);
+      } else {
+        setDataSource([]);
+      }
+    }
+  }, [props.isShowGranted, selectKeys, props.data]);
+
+  useEffect(() => {
+    if (!props.isShowGranted) {
+      if (props.data && props.data.length && props.operations) {
+        getOperations(props.data, props.operations);
+      } else {
+        setDataSource([]);
+      }
+    }
+  }, [props.data, props.operations, props.isShowGranted]);
+
+  useEffect(() => {
+    getApiGrant();
+  }, []);
+
+  const save = useCallback(async () => {
+    const param = new URLSearchParams(location.search);
+    const code = param.get('code');
+    const operations = selectKeys.map((a: string) => {
+      const item = dataSource.find((b) => b.operationId === a);
+      return {
+        id: a,
+        permissions: item.security,
+      };
+    });
+
+    setLoading(true);
+    const resp = await service.saveApiGrant(code!, { operations });
+    setLoading(false);
+    if (resp.status === 200) {
+      message.success('操作成功');
+    }
+  }, [selectKeys, location, dataSource]);
 
-  const save = useCallback(async () => {}, [selectKeys]);
+  console.log(dataSource);
 
   return (
     <div className={'platforms-api-table'}>
       <Table<any>
+        rowKey={'operationId'}
         columns={[
           {
             title: 'API',
-            dataIndex: 'name',
+            dataIndex: 'url',
             render: (text: string, record) => {
               return (
                 <Button
                   type={'link'}
                   style={{ padding: 0 }}
                   onClick={() => {
-                    props.onJump(record.id);
+                    ApiModel.swagger = record;
+                    ApiModel.showTable = false;
                   }}
                 >
                   {text}
@@ -47,30 +108,44 @@ export default (props: TableProps) => {
           },
           {
             title: '说明',
-            dataIndex: '',
+            dataIndex: 'summary',
           },
         ]}
+        pagination={false}
         dataSource={dataSource}
-        rowSelection={{
-          selectedRowKeys: selectKeys,
-          onSelect: (record, selected) => {
-            if (selected) {
-              const newArr = [...selectKeys, record];
-              setSelectKeys(newArr);
-            } else {
-              setSelectKeys([...selectKeys.filter((key) => key !== record)]);
-            }
-          },
-          onSelectAll: (_, selectedRows) => {
-            setSelectKeys(selectedRows);
-          },
-        }}
+        rowSelection={
+          props.isShowGranted !== true
+            ? {
+                selectedRowKeys: selectKeys,
+                onSelect: (record, selected) => {
+                  if (selected) {
+                    const newArr = [...selectKeys, record.operationId];
+                    setSelectKeys(newArr);
+                  } else {
+                    setSelectKeys([...selectKeys.filter((key) => key !== record.operationId)]);
+                  }
+                },
+                onSelectAll: (selected, selectedRows) => {
+                  if (selected) {
+                    setSelectKeys(
+                      selectedRows.filter((item) => !!item).map((item) => item.operationId),
+                    );
+                  } else {
+                    setSelectKeys([]);
+                  }
+                },
+              }
+            : undefined
+        }
+        scroll={{ y: 600 }}
       />
-      <div className={'platforms-api-save'}>
-        <Button type={'primary'} onClick={save}>
-          保存
-        </Button>
-      </div>
+      {props.isShowGranted !== true && (
+        <div className={'platforms-api-save'}>
+          <Button type={'primary'} onClick={save} loading={loading}>
+            保存
+          </Button>
+        </div>
+      )}
     </div>
   );
 };

+ 60 - 0
src/pages/system/Platforms/Api/index.less

@@ -1,3 +1,5 @@
+@import '~antd/es/style/themes/default.less';
+
 .platforms-api {
   display: flex;
   padding: 24px;
@@ -5,6 +7,8 @@
 
   .platforms-api-tree {
     width: 320px;
+    padding-right: 12px;
+    border-right: 1px solid #e9e9e9;
   }
 
   .platforms-api-table {
@@ -12,9 +16,65 @@
     flex-direction: column;
     flex-grow: 1;
     width: 0;
+    margin-left: 24px;
 
     .platforms-api-save {
       margin-top: 12px;
     }
   }
+
+  .platforms-api-swagger {
+    flex: 1;
+    margin-left: 24px;
+
+    .platforms-api-swagger-back {
+      margin-bottom: 24px;
+    }
+
+    .platforms-api-swagger-content {
+      .swagger-content-title {
+        font-weight: bold;
+        font-size: 16px;
+      }
+
+      .swagger-content-item,
+      .swagger-content-url {
+        margin-top: 24px;
+      }
+
+      .swagger-content-url .url-method {
+        color: #fff;
+        border: none;
+
+        &.put {
+          background-color: @orange-6;
+        }
+
+        &.delete {
+          background-color: @red-6;
+        }
+
+        &.post {
+          background-color: @green-6;
+        }
+
+        &.get {
+          background-color: @blue-6;
+        }
+
+        &.patch {
+          background-color: @lime-6;
+        }
+      }
+
+      .swagger-content-request-type {
+        display: flex;
+        justify-content: space-between;
+
+        > span:nth-child(odd) {
+          font-weight: bold;
+        }
+      }
+    }
+  }
 }

+ 2 - 18
src/pages/system/Platforms/Api/index.tsx

@@ -1,26 +1,10 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import Table from './basePage';
-import Tree from './leftTree';
-import './index.less';
-import { useState } from 'react';
+import BasePage from './base';
 
 export default () => {
-  const [jumpId, setJumpId] = useState('');
-  const [parentId, setParentId] = useState('');
-
   return (
     <PageContainer>
-      <div className={'platforms-api'}>
-        <div className={'platforms-api-tree'}>
-          <Tree
-            onSelect={(id) => {
-              setJumpId('');
-              setParentId(id);
-            }}
-          />
-        </div>
-        {!jumpId ? <Table parentId={parentId} onJump={setJumpId} /> : <></>}
-      </div>
+      <BasePage />
     </PageContainer>
   );
 };

+ 48 - 47
src/pages/system/Platforms/Api/leftTree.tsx

@@ -1,9 +1,10 @@
 import { Tree } from 'antd';
-import React, { useState } from 'react';
-import { queryChannel } from '@/pages/media/SplitScreen/service';
+import React, { useEffect, useState } from 'react';
+import { service } from '@/pages/system/Platforms';
+import { ApiModel } from '@/pages/system/Platforms/Api/base';
 
 type LeftTreeType = {
-  onSelect: (id: string) => void;
+  onSelect: (data: any) => void;
 };
 
 interface DataNode {
@@ -17,15 +18,11 @@ interface DataNode {
 export default (props: LeftTreeType) => {
   const [treeData, setTreeData] = useState<DataNode[]>([]);
 
-  /**
-   * 是否为子节点
-   * @param node
-   */
-  const isLeaf = (node: DataNode): boolean => {
-    if (node.children) {
-      return false;
+  const getLevelOne = async () => {
+    const resp = await service.getApiFirstLevel();
+    if (resp.urls && resp.urls.length) {
+      setTreeData(resp.urls.map((item: any) => ({ ...item, id: item.url })));
     }
-    return true;
   };
 
   const updateTreeData = (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] => {
@@ -47,29 +44,37 @@ export default (props: LeftTreeType) => {
     });
   };
 
-  const getChildren = (key: React.Key, params: any): Promise<any> => {
+  const handleTreeData = (data: any) => {
+    const newArr = data.tags.map((item: any) => ({ id: item.name, name: item.name, isLeaf: true }));
+
+    Object.keys(data.paths).forEach((a: any) => {
+      Object.keys(data.paths[a]).forEach((b) => {
+        const { tags, ...extraData } = data.paths[a][b];
+        const tag = tags[0];
+        const obj = {
+          url: a,
+          method: b,
+          ...extraData,
+        };
+        const item = newArr.find((c: any) => c.id === tag);
+        if (item) {
+          item.extraData = item.extraData ? [...item.extraData, obj] : [obj];
+        }
+      });
+    });
+    console.log(newArr);
+    return newArr;
+  };
+
+  const getChildren = (key: string, name: string): Promise<any> => {
     return new Promise(async (resolve) => {
-      const resp = await queryChannel(params);
-      if (resp.status === 200) {
-        const { total, pageIndex, pageSize } = resp.result;
+      const resp = await service.getApiNextLevel(name);
+      if (resp) {
+        ApiModel.components = resp.components;
+        ApiModel.baseUrl = resp.servers[0].url;
+        const handleData = handleTreeData(resp);
         setTreeData((origin) => {
-          const data = updateTreeData(
-            origin,
-            key,
-            resp.result.data.map((item: DataNode) => ({
-              ...item,
-              isLeaf: isLeaf(item),
-            })),
-          );
-
-          if (total > (pageIndex + 1) * pageSize) {
-            setTimeout(() => {
-              getChildren(key, {
-                ...params,
-                pageIndex: params.pageIndex + 1,
-              });
-            }, 50);
-          }
+          const data = updateTreeData(origin, key, handleData);
 
           return data;
         });
@@ -78,38 +83,34 @@ export default (props: LeftTreeType) => {
     });
   };
 
-  const onLoadData = ({ key, children }: any): Promise<void> => {
+  const onLoadData = (node: any): Promise<void> => {
+    console.log(node);
     return new Promise(async (resolve) => {
-      if (children) {
+      if (node.children) {
         resolve();
         return;
       }
-      await getChildren(key, {
-        pageIndex: 0,
-        pageSize: 100,
-        terms: [
-          {
-            column: 'deviceId',
-            value: key,
-          },
-        ],
-      });
+      await getChildren(node.key, node.name);
       resolve();
     });
   };
 
+  useEffect(() => {
+    getLevelOne();
+  }, []);
+
   return (
     <Tree
       showIcon
       showLine={{ showLeafIcon: false }}
-      height={550}
+      height={700}
       fieldNames={{
         title: 'name',
         key: 'id',
       }}
       onSelect={(_, { node }: any) => {
-        if (props.onSelect && node.isLeaf) {
-          props.onSelect(node.id);
+        if (node.isLeaf && props.onSelect) {
+          props.onSelect(node.extraData);
         }
       }}
       loadData={onLoadData}

+ 360 - 0
src/pages/system/Platforms/Api/swagger-ui/base.tsx

@@ -0,0 +1,360 @@
+import { observer } from '@formily/react';
+import { ApiModel } from '@/pages/system/Platforms/Api/base';
+import { TitleComponent } from '@/components';
+import ReactJson from 'react-json-view';
+import { Button, Input, Table, Tabs } from 'antd';
+import { useCallback, useEffect, useState } from 'react';
+import { cloneDeep, isArray, isObject } from 'lodash';
+import classNames from 'classnames';
+
+export default observer(() => {
+  const [dataSource, setDataSource] = useState<any[]>([]);
+  const [responseData, setResponseData] = useState<any[]>([]);
+
+  const getContent = (data: any) => {
+    return Object.keys(data)[0];
+  };
+
+  const ObjectFindValue = (name: string, obj: any): any => {
+    let value: any = '';
+    if (obj[name]) {
+      value = obj[name];
+    } else {
+      Object.keys(obj).some((key) => {
+        const _value = isObject(obj[key]) ? ObjectFindValue(name, obj[key]) : undefined;
+        if (_value) {
+          value = _value;
+          return true;
+        }
+        return false;
+      });
+    }
+    return value;
+  };
+
+  const titleCase = (value: string) => {
+    return value.slice(0, 1).toLowerCase() + value.slice(1);
+  };
+
+  const handleEntityTable = useCallback(
+    (entityName: string, entityData: any, entityType: string, required: boolean) => {
+      let propertiesData: any[] = [];
+
+      if (entityData) {
+        propertiesData = Object.keys(entityData).map((key) => {
+          return {
+            name: key,
+            description: entityData[key].description,
+            method: '',
+            required: !!entityData[key].required,
+            type: entityData[key].type,
+          };
+        });
+      }
+      // 数组类型,实体名末尾加s
+      const _isArray = entityType === 'array' ? 's' : '';
+
+      setDataSource([
+        ...dataSource,
+        {
+          name: titleCase(entityName) + _isArray,
+          description: entityName,
+          method: 'body',
+          required: required,
+          type: entityType || entityName,
+          children: propertiesData,
+        },
+      ]);
+    },
+    [dataSource],
+  );
+
+  const getEntity = () => {
+    const contentType: any = Object.values(ApiModel.swagger.requestBody.content);
+    if (contentType) {
+      const refUrl = ObjectFindValue('$ref', ApiModel.swagger.requestBody.content);
+      if (refUrl) {
+        const entityName = refUrl.split('/').pop();
+        const entityType = ObjectFindValue('type', ApiModel.swagger.requestBody.content);
+        const entityRequired = ApiModel.swagger.requestBody.required;
+        const entity: any = ApiModel.components.schemas[entityName];
+        const file = ObjectFindValue('file', ApiModel.swagger.requestBody.content);
+        // 是否为文件上传
+        if (file && isObject(file)) {
+          const fileObj = [
+            {
+              name: 'file',
+              description: '',
+              method: 'query',
+              required: true,
+              type: 'file',
+            },
+          ];
+          setDataSource(fileObj);
+          ApiModel.debugger.params = fileObj;
+        } else if (entity) {
+          handleEntityTable(entityName, entity.properties || entity, entityType, !!entityRequired);
+        }
+        return entityType === 'array' ? [entity.properties || entity] : entity.properties || entity;
+      }
+      return '';
+    }
+    return '';
+  };
+
+  const handleEntity = (entityData: any): any => {
+    let newEntity = {};
+    console.log(entityData);
+    if (isArray(entityData)) {
+      newEntity = [handleEntity(entityData[0])];
+    } else if (isObject(entityData)) {
+      Object.keys(entityData).forEach((key) => {
+        const type = entityData[key].type;
+        console.log(entityData[key]);
+        if (type) {
+          if (type.includes('integer')) {
+            newEntity[key] = 0;
+          } else if (type === 'boolean') {
+            newEntity[key] = true;
+          } else if (type === 'object') {
+            newEntity[key] = {};
+          } else if (type === 'array') {
+            newEntity[key] = [];
+          } else {
+            newEntity[key] = '';
+          }
+        } else {
+          newEntity[key] = '';
+        }
+      });
+    }
+    return newEntity;
+  };
+
+  const getResult = (name: string, oldName: string = '') => {
+    const entity = cloneDeep(ApiModel.components.schemas[name].properties);
+    console.log(entity);
+    if (name === oldName) {
+      // 禁止套娃
+      return [];
+    }
+    Object.keys(entity).forEach((key) => {
+      const type = entity[key].type;
+      if ((entity[key].items && entity[key].items.$ref) || entity[key].$ref) {
+        const _ref = entity[key].$ref || entity[key].items.$ref;
+        const refName = _ref.split('/').pop();
+        if (type === 'array') {
+          entity[key] = [getResult(refName, name)];
+        } else {
+          entity[key] = getResult(refName, name);
+        }
+      } else if (type) {
+        if (type.includes('integer')) {
+          entity[key] = 0;
+        } else if (type === 'boolean') {
+          entity[key] = true;
+        } else {
+          entity[key] = '';
+        }
+      }
+    });
+    return entity;
+  };
+
+  const handleResponseParam = (name: any, oldName: string = ''): any[] => {
+    if (!ApiModel.components.schemas[name]) {
+      return [];
+    }
+
+    const entity = cloneDeep(ApiModel.components.schemas[name].properties);
+    console.log(entity);
+
+    const newArr: any[] = [];
+    if (name === oldName) {
+      return newArr;
+    }
+
+    Object.keys(entity).forEach((key) => {
+      const type = entity[key].type;
+      const obj: any = {
+        code: key,
+        description: entity[key].description,
+        type: type,
+      };
+
+      if ((entity[key].items && entity[key].items.$ref) || entity[key].$ref) {
+        const _ref = entity[key].$ref || entity[key].items.$ref;
+        const refName = _ref.split('/').pop();
+        if (refName) {
+          obj.type = refName;
+          obj.children = handleResponseParam(refName, name);
+        }
+      }
+      newArr.push(obj);
+    });
+    return newArr;
+  };
+
+  const handleResponse = () => {
+    const newArr: any[] = [];
+    Object.keys(ApiModel.swagger.responses).forEach((key) => {
+      const refUrl = ObjectFindValue('$ref', ApiModel.swagger.responses[key]);
+      const entityName = refUrl.split('/').pop();
+
+      newArr.push({
+        code: key,
+        description: ApiModel.swagger.responses[key].description,
+        schema: key !== '400' ? entityName : '',
+        entityName: entityName,
+        result: key !== '400' ? getResult(entityName) : {},
+      });
+    });
+    setResponseData(newArr);
+  };
+
+  useEffect(() => {
+    if (ApiModel.swagger.parameters) {
+      const params = ApiModel.swagger.parameters.map((item: any) => {
+        return {
+          name: item.name,
+          required: item.required,
+          type: item.schema.type,
+          description: item.description,
+          method: item.in,
+        };
+      });
+      ApiModel.debugger.params = params;
+      setDataSource(params);
+    }
+    if (ApiModel.swagger.requestBody) {
+      ApiModel.debugger.body = handleEntity(getEntity());
+    }
+
+    if (ApiModel.swagger.responses) {
+      handleResponse();
+    }
+  }, []);
+
+  return (
+    <div className={'platforms-api-swagger-content'}>
+      <div className={'swagger-content-title'}>{ApiModel.swagger.summary}</div>
+      <div className={'swagger-content-url'}>
+        <Input.Group compact>
+          <Button className={classNames('url-method', ApiModel.swagger.method)}>
+            {ApiModel.swagger.method ? ApiModel.swagger.method.toUpperCase() : ''}
+          </Button>
+          <Input
+            style={{
+              width: `calc(100% - ${ApiModel.swagger.method !== 'delete' ? '70px' : '80px'})`,
+            }}
+            value={ApiModel.swagger.url}
+            readOnly
+          />
+        </Input.Group>
+      </div>
+      <div className={'swagger-content-item swagger-content-request-type'}>
+        <span>请求数据类型</span>
+        <span>
+          {ApiModel.swagger.requestBody
+            ? getContent(ApiModel.swagger.requestBody.content)
+            : 'application/x-www-form-urlencoded'}
+        </span>
+        <span>响应数据类型</span>
+        <span>{`["/"]`}</span>
+      </div>
+      {ApiModel.swagger.description && (
+        <div className={'swagger-content-item'}>
+          <TitleComponent data={'接口描述'} />
+          <div> {ApiModel.swagger.description} </div>
+        </div>
+      )}
+      {ApiModel.swagger.requestBody &&
+        ApiModel.debugger.body &&
+        !!Object.keys(ApiModel.debugger.body).length && (
+          <div className={'swagger-content-item'}>
+            <TitleComponent data={'请求示例'} />
+            <div>
+              {
+                // @ts-ignore
+                <ReactJson
+                  displayObjectSize={false}
+                  displayDataTypes={false}
+                  name={false}
+                  src={ApiModel.debugger.body}
+                />
+              }
+            </div>
+          </div>
+        )}
+      <div className={'swagger-content-item'}>
+        <TitleComponent data={'请求参数'} />
+        <Table
+          pagination={false}
+          size={'small'}
+          columns={[
+            { title: '参数名', dataIndex: 'name' },
+            { title: '参数说明', dataIndex: 'description' },
+            { title: '请求类型', dataIndex: 'method' },
+            {
+              title: '是否必须',
+              dataIndex: 'required',
+              render: (text) => <span>{`${!!text}`}</span>,
+            },
+            { title: '参数类型', dataIndex: 'type' },
+          ]}
+          dataSource={dataSource}
+        />
+      </div>
+      <div className={'swagger-content-item'}>
+        <TitleComponent data={'响应状态'} />
+        <Table
+          pagination={false}
+          size={'small'}
+          columns={[
+            { title: '状态码', dataIndex: 'code' },
+            { title: '说明', dataIndex: 'description' },
+            { title: 'schema', dataIndex: 'schema' },
+          ]}
+          dataSource={responseData}
+        />
+      </div>
+      <div className={'swagger-content-item'}>
+        <Tabs>
+          {responseData
+            .filter((item) => item.code !== '400')
+            .map((item) => {
+              console.log(item);
+              return (
+                <Tabs.TabPane key={item.code} tab={item.code}>
+                  <div>
+                    <div>
+                      <TitleComponent data={'响应参数'} style={{ margin: 0 }} />
+                      <Table
+                        pagination={false}
+                        size={'small'}
+                        columns={[
+                          { title: '参数名称', dataIndex: 'code' },
+                          { title: '参数说明', dataIndex: 'description' },
+                          { title: '类型', dataIndex: 'type' },
+                        ]}
+                        dataSource={handleResponseParam(item.entityName)}
+                      />
+                    </div>
+                    {
+                      // @ts-ignore
+                      <ReactJson
+                        displayObjectSize={false}
+                        displayDataTypes={false}
+                        name={false}
+                        src={item.result}
+                      />
+                    }
+                  </div>
+                </Tabs.TabPane>
+              );
+            })}
+        </Tabs>
+      </div>
+    </div>
+  );
+});

+ 254 - 0
src/pages/system/Platforms/Api/swagger-ui/debugging.tsx

@@ -0,0 +1,254 @@
+import { TitleComponent } from '@/components';
+import ReactJson from 'react-json-view';
+import { request } from 'umi';
+import MonacoEditor from 'react-monaco-editor';
+import { Button, Input } from 'antd';
+import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
+import { observer } from '@formily/react';
+import { ApiModel } from '@/pages/system/Platforms/Api/base';
+import { createForm } from '@formily/core';
+import { createSchemaField, FormProvider } from '@formily/react';
+import { FormItem, Input as FormilyInput, ArrayTable, Editable } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+import classNames from 'classnames';
+
+export default observer(() => {
+  const [result, setResult] = useState({});
+  const [body, setBody] = useState({});
+
+  const editor: any = useRef(null);
+
+  useEffect(() => {
+    if (ApiModel.debugger.body && editor.current) {
+      const { editor: MEditor } = editor.current;
+      MEditor.setValue(JSON.stringify(ApiModel.debugger.body));
+      setTimeout(() => {
+        MEditor.getAction('editor.action.formatDocument').run();
+      }, 300);
+      // MEditor.trigger('anyString', 'editor.action.formatDocument');//自动格式化代码
+      MEditor.setValue(MEditor.getValue());
+    }
+  }, [ApiModel.debugger, editor.current]);
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Editable,
+      Input: FormilyInput,
+      ArrayTable,
+    },
+  });
+
+  const form = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+      }),
+    [],
+  );
+
+  const onSearch = useCallback(async () => {
+    const formData: any = await form.submit();
+    console.log(formData);
+    let newUrl = ApiModel.swagger.url;
+    if (formData && formData.params && formData.params.length) {
+      const params = formData.params;
+      params.forEach((item: any) => {
+        if (newUrl.includes(`{${item.name}}`)) {
+          newUrl = newUrl.replace(`{${item.name}}`, item.values);
+        }
+      });
+      console.log(newUrl);
+    }
+
+    // 判断请求类型
+    const method = ApiModel.swagger.method && ApiModel.swagger.method.toUpperCase();
+    let options = {};
+    if (['POST', 'PUT', 'PATCH'].includes(method)) {
+      options = {
+        method,
+        data: body || {},
+      };
+    } else if (['GET', 'DELETE'].includes(method)) {
+      options = {
+        method,
+        params: body || {},
+      };
+    }
+
+    request(`${ApiModel.baseUrl}${newUrl}`, options).then((resp) => {
+      if (resp.status === 200) {
+        setResult(resp);
+      } else {
+        resp
+          .clone()
+          .text()
+          .then((res: string) => {
+            if (res) {
+              setResult(JSON.parse(res));
+            } else {
+              resp
+                .clone()
+                .json()
+                .then((res2: any) => {
+                  setResult(res2);
+                });
+            }
+          });
+      }
+    });
+  }, [body]);
+
+  useEffect(() => {
+    if (form && ApiModel.debugger && ApiModel.debugger.params) {
+      const arr = ApiModel.debugger.params.map((item: any) => {
+        return {
+          name: item.name,
+          values: '',
+        };
+      });
+      form.setValues({ params: arr });
+    }
+  }, [form, ApiModel.debugger]);
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      params: {
+        type: 'array',
+        'x-decorator': 'FormItem',
+        'x-component': 'ArrayTable',
+        'x-component-props': {
+          pagination: { pageSize: 10 },
+          scroll: { x: '100%' },
+        },
+        items: {
+          type: 'object',
+          properties: {
+            column1: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { title: '参数名称' },
+              properties: {
+                name: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                  required: true,
+                },
+              },
+            },
+            column2: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { title: '参数名称' },
+              properties: {
+                values: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                  required: true,
+                },
+              },
+            },
+            column6: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: '操作',
+                dataIndex: 'operations',
+                width: 100,
+                fixed: 'right',
+                align: 'center',
+              },
+              properties: {
+                item: {
+                  type: 'void',
+                  'x-component': 'FormItem',
+                  properties: {
+                    remove: {
+                      type: 'void',
+                      'x-component': 'ArrayTable.Remove',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+        properties: {
+          add: {
+            type: 'void',
+            'x-component': 'ArrayTable.Addition',
+            title: '新增',
+          },
+        },
+      },
+    },
+  };
+
+  return (
+    <div className={'platforms-api-swagger-content'}>
+      <div className={'swagger-content-title'}>{ApiModel.swagger.summary}</div>
+      <div className={'swagger-content-url'}>
+        <Input.Group compact>
+          <Button className={classNames('url-method', ApiModel.swagger.method)}>
+            {ApiModel.swagger.method ? ApiModel.swagger.method.toUpperCase() : ''}
+          </Button>
+          <Input
+            allowClear
+            style={{
+              width: `calc(100% - ${ApiModel.swagger.method !== 'delete' ? '140px' : '150px'})`,
+            }}
+            value={ApiModel.swagger.url}
+          />
+          <Button type="primary" onClick={onSearch}>
+            发送
+          </Button>
+        </Input.Group>
+      </div>
+      <div className={'swagger-content-item'}>
+        <TitleComponent data={'请求参数'} />
+        <div>
+          {ApiModel.debugger.params && (
+            <FormProvider form={form}>
+              <SchemaField schema={schema} />
+            </FormProvider>
+          )}
+          {ApiModel.debugger.body && (
+            <MonacoEditor
+              height={200}
+              language={'json'}
+              theme={'dark'}
+              ref={editor}
+              onChange={(value) => {
+                try {
+                  setBody(JSON.parse(value));
+                } catch (e) {
+                  console.warn(e);
+                }
+              }}
+              editorDidMount={(_editor) => {
+                _editor.getAction('editor.action.formatDocument').run();
+              }}
+            />
+          )}
+        </div>
+      </div>
+      <div className={'swagger-content-item'}>
+        <TitleComponent data={'响应内容'} />
+        <div>
+          {
+            // @ts-ignore
+            <ReactJson
+              displayObjectSize={false}
+              displayDataTypes={false}
+              name={false}
+              src={result}
+            />
+          }
+        </div>
+      </div>
+    </div>
+  );
+});

+ 33 - 0
src/pages/system/Platforms/Api/swagger-ui/index.tsx

@@ -0,0 +1,33 @@
+import { Button, Tabs } from 'antd';
+import { ApiModel } from '@/pages/system/Platforms/Api/base';
+import Base from './base';
+import Debugger from './debugging';
+
+interface SwaggerProps {
+  showDebugger?: boolean;
+}
+
+export default (props: SwaggerProps) => {
+  return (
+    <div className={'platforms-api-swagger'}>
+      <Button
+        onClick={() => {
+          ApiModel.showTable = true;
+        }}
+        className={'platforms-api-swagger-back'}
+      >
+        返回
+      </Button>
+      <Tabs type="card">
+        <Tabs.TabPane tab={'文档'} key={1}>
+          <Base />
+        </Tabs.TabPane>
+        {props.showDebugger === true && (
+          <Tabs.TabPane tab={'调试'} key={2}>
+            <Debugger />
+          </Tabs.TabPane>
+        )}
+      </Tabs>
+    </div>
+  );
+};

+ 94 - 0
src/pages/system/Platforms/View/index.tsx

@@ -0,0 +1,94 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { Button, Card, Col, Popover, Row } from 'antd';
+import ApiPage from '../Api/base';
+import { useEffect, useState } from 'react';
+import { useLocation } from 'umi';
+import { service } from '@/pages/system/Platforms';
+
+const defaultHeight = 50;
+
+export default () => {
+  const location = useLocation();
+
+  const [clientId, setClientId] = useState('');
+  const [secureKey, setSecureKey] = useState('');
+
+  const getDetail = async (id: string) => {
+    const resp = await service.getDetail(id);
+    if (resp.status === 200) {
+      setClientId(resp.result.id);
+      setSecureKey(resp.result.secureKey);
+    }
+  };
+
+  useEffect(() => {
+    const param = new URLSearchParams(location.search);
+    const code = param.get('code');
+    if (code) {
+      getDetail(code);
+    }
+  }, [location]);
+
+  useEffect(() => {
+    //  请求SDK下载地址
+  }, []);
+
+  const downLoadJDK = (
+    <div>
+      <div
+        style={{
+          width: 300,
+          height: 120,
+          padding: 12,
+          border: '1px solid #e9e9e9',
+          borderRadius: 2,
+          marginBottom: 12,
+        }}
+      >
+        暂时没有接口
+      </div>
+      <div>
+        <Button type={'primary'}>jar下载</Button>
+      </div>
+    </div>
+  );
+
+  return (
+    <PageContainer>
+      <Row gutter={[16, 16]}>
+        <Col span={24}>
+          <Row gutter={16}>
+            <Col span={12}>
+              <Card title="基本信息">
+                <div style={{ height: defaultHeight }}>
+                  <div>
+                    <span style={{ fontWeight: 'bold', fontSize: 16 }}>clientId: </span>
+                    {clientId}
+                  </div>
+                  <div>
+                    <span style={{ fontWeight: 'bold', fontSize: 16 }}>secureKey: </span>
+                    {secureKey}
+                  </div>
+                </div>
+              </Card>
+            </Col>
+            <Col span={12}>
+              <Card title="SDK下载">
+                <div style={{ height: defaultHeight }}>
+                  <Popover trigger="click" title={'POM依赖'} content={downLoadJDK}>
+                    <Button> Java </Button>
+                  </Popover>
+                </div>
+              </Card>
+            </Col>
+          </Row>
+        </Col>
+        <Col span={24}>
+          <Card title={'API文档'}>
+            <ApiPage showDebugger={true} isShowGranted={true} />
+          </Card>
+        </Col>
+      </Row>
+    </PageContainer>
+  );
+};

+ 87 - 15
src/pages/system/Platforms/index.tsx

@@ -2,22 +2,31 @@ 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 { BadgeStatus, PermissionButton } from '@/components';
+import { useIntl, useHistory } from 'umi';
+import { BadgeStatus, PermissionButton, AIcon } from '@/components';
 import SearchComponent from '@/components/SearchComponent';
-import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
+import {
+  DeleteOutlined,
+  EditOutlined,
+  PlayCircleOutlined,
+  PlusOutlined,
+  StopOutlined,
+} from '@ant-design/icons';
 import { StatusColorEnum } from '@/components/BadgeStatus';
 import SaveModal from './save';
 import PasswordModal from './password';
 import Service from './service';
 import { message } from 'antd';
+import { getMenuPathByCode } from '@/utils/menu';
 
 export const service = new Service('api-client');
 
 export default () => {
   const actionRef = useRef<ActionType>();
   const intl = useIntl();
+  const history = useHistory();
   const [param, setParam] = useState({});
+  const [saveType, setSaveType] = useState<'save' | 'edit'>('save');
   const [saveVisible, setSaveVisible] = useState(false);
   const [passwordVisible, setPasswordVisible] = useState(false);
   const [editData, setEditData] = useState<any | undefined>(undefined);
@@ -41,10 +50,14 @@ export default () => {
       dataIndex: 'username',
       title: '用户名',
     },
-    {
-      dataIndex: 'roleIdList',
-      title: '角色',
-    },
+    // {
+    //   dataIndex: 'roleIdList',
+    //   title: '角色',
+    //   renderText: (record => {
+    //     console.log(record);
+    //     return ''
+    //   })
+    // },
     {
       dataIndex: 'state',
       title: intl.formatMessage({
@@ -60,7 +73,7 @@ export default () => {
             text={record.text}
             statusNames={{
               enabled: StatusColorEnum.processing,
-              disable: StatusColorEnum.error,
+              disabled: StatusColorEnum.error,
             }}
           />
         ) : (
@@ -106,6 +119,7 @@ export default () => {
             }),
           }}
           onClick={() => {
+            setSaveType('edit');
             setSaveVisible(true);
             setEditData(record);
           }}
@@ -116,30 +130,37 @@ export default () => {
           key={'empowerment'}
           type={'link'}
           style={{ padding: 0 }}
-          isPermission={permission.update}
+          isPermission={permission.empowerment}
           tooltip={{
             title: '赋权',
           }}
-          onClick={() => {}}
+          onClick={() => {
+            const url = getMenuPathByCode('system/Platforms/Api');
+            history.push(`${url}?code=${record.id}`);
+          }}
         >
-          <EditOutlined />
+          <AIcon type={'icon-fuquan'} />
         </PermissionButton>,
         <PermissionButton
           key={'api'}
           type={'link'}
           style={{ padding: 0 }}
+          isPermission={true}
           tooltip={{
             title: '查看API',
           }}
-          onClick={() => {}}
+          onClick={() => {
+            const url = getMenuPathByCode('system/Platforms/View');
+            history.push(`${url}?code=${record.id}`);
+          }}
         >
-          <EditOutlined />
+          <AIcon type={'icon-chakanAPI'} />
         </PermissionButton>,
         <PermissionButton
           key={'password'}
           type={'link'}
           style={{ padding: 0 }}
-          isPermission={permission.action}
+          isPermission={permission.update}
           tooltip={{
             title: '重置密码',
           }}
@@ -148,7 +169,56 @@ export default () => {
             setPasswordVisible(true);
           }}
         >
-          <EditOutlined />
+          <AIcon type={'icon-zhongzhimima'} />
+        </PermissionButton>,
+        <PermissionButton
+          key={'state'}
+          type={'link'}
+          style={{ padding: 0 }}
+          popConfirm={{
+            title: intl.formatMessage({
+              id: `pages.data.option.${
+                record.state.value !== 'disabled' ? 'disabled' : 'enabled'
+              }.tips`,
+              defaultMessage: '确认禁用?',
+            }),
+            onConfirm: () => {
+              if (record.state.value !== 'disabled') {
+                service.undeploy(record.id).then((resp: any) => {
+                  if (resp.status === 200) {
+                    message.success(
+                      intl.formatMessage({
+                        id: 'pages.data.option.success',
+                        defaultMessage: '操作成功!',
+                      }),
+                    );
+                    actionRef.current?.reload();
+                  }
+                });
+              } else {
+                service.deploy(record.id).then((resp: any) => {
+                  if (resp.status === 200) {
+                    message.success(
+                      intl.formatMessage({
+                        id: 'pages.data.option.success',
+                        defaultMessage: '操作成功!',
+                      }),
+                    );
+                    actionRef.current?.reload();
+                  }
+                });
+              }
+            },
+          }}
+          isPermission={permission.action}
+          tooltip={{
+            title: intl.formatMessage({
+              id: `pages.data.option.${record.state.value !== 'disabled' ? 'disabled' : 'enabled'}`,
+              defaultMessage: record.state.value !== 'disabled' ? '禁用' : '启用',
+            }),
+          }}
+        >
+          {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         <PermissionButton
           key={'delete'}
@@ -198,6 +268,7 @@ export default () => {
             type="primary"
             isPermission={permission.add}
             onClick={() => {
+              setSaveType('save');
               setSaveVisible(true);
             }}
             icon={<PlusOutlined />}
@@ -212,6 +283,7 @@ export default () => {
       <SaveModal
         visible={saveVisible}
         data={editData}
+        type={saveType}
         onCancel={() => {
           setSaveVisible(false);
           setEditData(undefined);

+ 31 - 9
src/pages/system/Platforms/save.tsx

@@ -1,4 +1,5 @@
-import { createForm, Field } from '@formily/core';
+import { createForm } from '@formily/core';
+import type { Field } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import {
   Checkbox,
@@ -14,19 +15,20 @@ import {
   TreeSelect,
 } from '@formily/antd';
 import { message, Modal } from 'antd';
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import * as ICONS from '@ant-design/icons';
 import { PlusOutlined } from '@ant-design/icons';
 import type { ISchema } from '@formily/json-schema';
 import { PermissionButton } from '@/components';
 import usePermissions from '@/hooks/permission';
 import { action } from '@formily/reactive';
-import { Response } from '@/utils/typings';
+import type { Response } from '@/utils/typings';
 import { service } from '@/pages/system/Platforms/index';
 import { randomString } from '@/utils/util';
 
 interface SaveProps {
   visible: boolean;
+  type: 'save' | 'edit';
   data?: any;
   onReload?: () => void;
   onCancel?: () => void;
@@ -77,13 +79,33 @@ export default (props: SaveProps) => {
     () =>
       createForm({
         validateFirst: true,
-        initialValues: props.data
-          ? { ...props.data, confirm_password: props.data.password }
-          : { enableOAuth2: true, id: randomString() },
       }),
     [props.data],
   );
 
+  const getDetail = async (id: string) => {
+    const resp = await service.getDetail(id);
+    if (resp.status === 200) {
+      form.setValues({
+        ...resp.result,
+        confirm_password: resp.result.password,
+      });
+    }
+  };
+
+  useEffect(() => {
+    if (props.visible) {
+      if (props.type === 'edit') {
+        getDetail(props.data.id);
+      } else {
+        form.setValues({
+          enableOAuth2: true,
+          id: randomString(),
+        });
+      }
+    }
+  }, [props.type, props.visible]);
+
   const schema: ISchema = {
     type: 'object',
     properties: {
@@ -292,7 +314,7 @@ export default (props: SaveProps) => {
                     const tab: any = window.open(`${origin}/#/system/role?save=true`);
                     tab!.onTabSaveSuccess = (value: any) => {
                       form.setFieldState('roleIdList', async (state) => {
-                        state.dataSource = await getRole().then((resp) =>
+                        state.dataSource = await getRole().then((resp: any) =>
                           resp.result?.map((item: Record<string, unknown>) => ({
                             ...item,
                             label: item.name,
@@ -409,7 +431,7 @@ export default (props: SaveProps) => {
     console.log(data);
     if (data) {
       setLoading(true);
-      const resp: any = props.data ? await service.update(data) : await service.save(data);
+      const resp: any = props.type === 'edit' ? await service.edit(data) : await service.save(data);
       setLoading(false);
       if (resp.status === 200) {
         if (props.onReload) {
@@ -419,7 +441,7 @@ export default (props: SaveProps) => {
         message.success('操作成功');
       }
     }
-  }, [props.data]);
+  }, [props.type]);
 
   return (
     <Modal

+ 35 - 0
src/pages/system/Platforms/service.ts

@@ -9,6 +9,10 @@ class Service extends BaseService<platformsType> {
       params,
     });
 
+  getDetail = (id: string) => request(`${this.uri}/${id}/detail`, { method: 'GET' });
+
+  edit = (data: any) => request(`${this.uri}/${data.id}`, { method: 'PUT', data });
+
   /**
    * 密码校验
    * @param type
@@ -25,6 +29,37 @@ class Service extends BaseService<platformsType> {
       method: 'POST',
       data,
     });
+
+  undeploy = (id: string) => request(`${this.uri}/${id}/disable`, { method: 'PUT' });
+
+  deploy = (id: string) => request(`${this.uri}/${id}/enable`, { method: 'PUT' });
+
+  getApiFirstLevel = () =>
+    request(`/${SystemConst.API_BASE}/v3/api-docs/swagger-config`, { method: 'GET' });
+
+  getApiNextLevel = (name: string) =>
+    request(`/${SystemConst.API_BASE}/v3/api-docs/${name}`, { method: 'GET' });
+
+  /**
+   * 对接口进行授权
+   * @param id 第三方平台的ID
+   * @param data
+   */
+  saveApiGrant = (id: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/api-client/${id}/grant`, { method: 'POST', data });
+
+  /**
+   * 获取已授权的接口ID
+   * @param id 第三方平台的ID
+   */
+  getApiGranted = (id: string) =>
+    request(`/${SystemConst.API_BASE}/api-client/${id}/granted`, { method: 'GET' });
+
+  /**
+   * 获取可授权的接口ID
+   */
+  apiOperations = () =>
+    request(`/${SystemConst.API_BASE}//api-client/operations`, { method: 'GET' });
 }
 
 export default Service;

+ 4 - 1
src/utils/menu/index.ts

@@ -54,7 +54,10 @@ const extraRouteObj = {
     children: [{ code: 'AMap', name: '地图' }],
   },
   'system/Platforms': {
-    children: [{ code: 'Api', name: '赋权' }],
+    children: [
+      { code: 'Api', name: '赋权' },
+      { code: 'View', name: 'Api详情' },
+    ],
   },
 };
 //额外路由

+ 3 - 0
src/utils/menu/router.ts

@@ -118,6 +118,8 @@ export enum MENUS_CODE {
   'Northbound/AliCloud' = 'Northbound/AliCloud',
   'Northbound/AliCloud/Detail' = 'Northbound/AliCloud/Detail',
   'system/Platforms' = 'system/Platforms',
+  'system/Platforms/Api' = 'system/Platforms/Api',
+  'system/Platforms/View' = 'system/Platforms/View',
 }
 
 export type MENUS_CODE_TYPE = keyof typeof MENUS_CODE | string;
@@ -139,6 +141,7 @@ export enum BUTTON_PERMISSION_ENUM {
   'debug' = 'debug',
   'log' = 'log',
   'tigger' = 'tigger',
+  'empowerment' = 'empowerment',
 }
 
 // 调试按钮、通知记录、批量导出、批量导入、选择通道、推送、分配资产、绑定用户对应的ID是啥