自建API接口开发

This commit is contained in:
暮光:城中城
2021-12-25 20:46:19 +08:00
parent 7ce74938e8
commit 208826df62
38 changed files with 507 additions and 47 deletions

View File

@@ -0,0 +1,78 @@
const methodArray = ["get", "head", "post", "put", "patch", "delete", "options", "trace"];
/**
* 按tag分组获取左侧菜单目录树
* @param customRequest 原始文档信息
* @param tagPathMap 分组信息{分组名: {url: {...接口信息, path: '', url: '', method: ''}}}
* @param keywords 过滤关键字
* @param metaInfo 接口元信息点击时放入URL的参数
*/
export function getTreeDataForTag(customRequest, keywords, metaInfo) {
let firstChild = customRequest[0];
let treeData = getTreeDataChildren(firstChild, keywords, metaInfo, 1);
return [
{
key: 'main',
title: firstChild.name || '自建API接口文档',
children: treeData
}
];
}
function getTreeDataChildren(customRequest, keywords, metaInfo, treeIndex) {
let treeData = [];
if (!customRequest) {
return treeData;
}
let indexFolder = 1;
let indexApi = 1;
if (customRequest.children && customRequest.children.length > 0) {
customRequest.children.forEach(item => {
let tempTreeId = treeIndex + "_" + indexFolder + "_" + indexApi;
let treeChildren = getTreeDataChildren(item, keywords, metaInfo, tempTreeId);
treeData.push({title: item.name, key: tempTreeId, children: treeChildren});
indexApi++;
});
}
if (customRequest.apis && customRequest.apis.length > 0) {
customRequest.apis.forEach(item => {
let tempTreeId = treeIndex + "_" + indexFolder + "_" + indexApi;
treeData.push({
title: item.apiName,
key: tempTreeId,
isLeaf: true,
method: item.method,
query: {
...metaInfo,
path: item.apiUrl,
method: item.method,
}
});
indexApi++;
});
}
indexFolder++;
return treeData;
}
/**
* 搜索接口是否包含某关键字将匹配URL、path、method、summary、description、tags 属性
* @param url 接口URL
* @param methodNode 接口基本信息
* @param keywords 关键字
* @returns {*|boolean} 是否包含
*/
function searchInPathMethods(url, methodNode, keywords) {
if (!keywords || !url) {
return true;
}
url = url.toLowerCase();
keywords = keywords.toLowerCase();
// 路径中有就不用再去找了
if (url.indexOf(keywords) >= 0) {
return true;
}
let searchData = methodNode.path + methodNode.method + methodNode.summary + methodNode.description + methodNode.tags;
return (searchData && searchData.toLowerCase().indexOf(keywords) >= 0);
}

View File

@@ -15,6 +15,7 @@
<template v-if="docChoice && docChoice.docType">
<DocTreeSwagger v-if="docChoice.docType === 1 || docChoice.docType === 2" ref="swaggerRef"></DocTreeSwagger>
<DocTreeOpenApi v-if="docChoice.docType === 3 || docChoice.docType === 4" ref="openApiRef"></DocTreeOpenApi>
<CustomRequest v-if="docChoice.docType === 5" ref="customRequestRef"></CustomRequest>
</template>
</a-spin>
</div>
@@ -30,6 +31,7 @@
import {zyplayerApi} from '../../api'
import DocTreeSwagger from './doc-tree/Swagger.vue'
import DocTreeOpenApi from './doc-tree/OpenApi.vue'
import CustomRequest from './doc-tree/CustomRequest.vue'
export default {
props: {
@@ -38,7 +40,7 @@
default: false
},
},
components: {MenuChildrenLayout, DocTreeSwagger, DocTreeOpenApi},
components: {MenuChildrenLayout, DocTreeSwagger, DocTreeOpenApi, CustomRequest},
setup(props) {
const store = useStore();
const route = useRoute();
@@ -78,6 +80,7 @@
};
let swaggerRef = ref();
let openApiRef = ref();
let customRequestRef = ref();
const loadDoc = async () => {
treeDataLoading.value = true;
docChoice.value = docResourceList.value.find(item => item.id === docChoiceId.value);
@@ -101,6 +104,10 @@
if (openApiRef.value) {
openApiRef.value.loadDoc(docChoiceId.value, searchKeywords.value, loadDocCallback);
}
} else if (docChoice.value.docType === 5) {
if (customRequestRef.value) {
customRequestRef.value.loadDoc(docChoiceId.value, searchKeywords.value, loadDocCallback);
}
}
zyplayerApi.docApiGlobalParamList({docId: docChoiceId.value}).then(res => {
let docGlobalParam = res.data || [];
@@ -121,6 +128,10 @@
if (openApiRef.value) {
openApiRef.value.loadTreeData(searchKeywords.value);
}
} else if (docChoice.value.docType === 5) {
if (customRequestRef.value) {
customRequestRef.value.loadTreeData(searchKeywords.value);
}
}
};
watch(store.getters.getDocChangedNum, () => {
@@ -152,6 +163,7 @@
searchKeywords,
swaggerRef,
openApiRef,
customRequestRef,
docChoice,
searchDoc,
docChoiceChange,

View File

@@ -85,7 +85,7 @@
}
},
addPageTab() {
this.$router.push({path: '/api/request', query: {id: this.apiRequestIndex++}});
this.$router.push({path: '/custom/request', query: {id: this.apiRequestIndex++}});
},
removePageTab(key) {
if (this.pageList.length === 1) {

View File

@@ -0,0 +1,137 @@
<template>
<a-directory-tree :showIcon="false" :tree-data="treeData" v-model:expandedKeys="expandedKeys" @select="docChecked">
<template #title="{ title, isLeaf, method, children, key }">
<template v-if="key === 'info'">
<file-text-outlined style="margin-right: 3px;"/>
</template>
<template v-if="isLeaf">
<a-tag color="pink" v-if="method === 'get'">get</a-tag>
<a-tag color="red" v-else-if="method === 'post'">post</a-tag>
<a-tag color="orange" v-else-if="method === 'put'">put</a-tag>
<a-tag color="green" v-else-if="method === 'head'">head</a-tag>
<a-tag color="cyan" v-else-if="method === 'patch'">patch</a-tag>
<a-tag color="blue" v-else-if="method === 'delete'">delete</a-tag>
<a-tag color="purple" v-else-if="method === 'options'">options</a-tag>
<a-tag color="purple" v-else-if="method === 'trace'">trace</a-tag>
</template>
<span style="margin: 0 6px 0 3px;">{{title}}</span>
<template v-if="children">
<a-badge :count="children.length" :number-style="{backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset'}"/>
<a-dropdown :trigger="['click']">
<span @click.stop="" style="padding: 3px 10px;"><ellipsis-outlined /></span>
<template #overlay>
<a-menu>
<a-menu-item>
<plus-outlined />
<a href="javascript:;"> 新建接口</a>
</a-menu-item>
<a-menu-item>
<folder-add-outlined />
<a href="javascript:;"> 新建文件夹</a>
</a-menu-item>
<a-menu-divider />
<a-menu-item>
<edit-outlined />
<a href="javascript:;"> 编辑</a>
</a-menu-item>
<a-menu-item>
<delete-outlined />
<a href="javascript:;"> 删除</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</template>
</a-directory-tree>
</template>
<script>
import {toRefs, ref, reactive, onMounted, watch, nextTick} from 'vue';
import { useRouter, useRoute } from "vue-router";
import {useStore} from 'vuex';
import { message } from 'ant-design-vue';
import {InfoCircleOutlined, FileTextOutlined, EllipsisOutlined, EditOutlined, DeleteOutlined, FolderAddOutlined, ApiOutlined, PlusOutlined} from '@ant-design/icons-vue';
import {zyplayerApi} from '../../../api'
import {getTreeDataForTag} from '../../../assets/core/CustomRequestTreeAnalysis.js'
export default {
components: {InfoCircleOutlined, FileTextOutlined, EllipsisOutlined, EditOutlined, DeleteOutlined, FolderAddOutlined, ApiOutlined, PlusOutlined},
setup() {
const store = useStore();
const route = useRoute();
const router = useRouter();
let tagPathMap = ref({});
let customRequestDoc = ref({});
let treeData = ref([]);
let expandedKeys = ref([]);
let choiceDocId = ref('');
const docChecked = (val, node) => {
if (node.node.key === 'info') {
router.push({path: '/custom/info'});
} else if (node.node.isLeaf) {
let dataRef = node.node.dataRef;
router.push({path: '/custom/view', query: dataRef.query});
}
};
const loadDoc = (docId, keyword, callback) => {
choiceDocId.value = docId;
zyplayerApi.apiDocApisDetail({id: docId}).then(res => {
let v2Doc = res.data;
if (!v2Doc && v2Doc.length != 1) {
callback(false);
message.error('获取文档数据失败');
return;
}
customRequestDoc.value = v2Doc;
store.commit('setCustomRequestDoc', v2Doc);
loadTreeData(keyword);
callback(true);
}).catch(() => {
callback(false);
});
};
const loadTreeData = async (keyword) => {
let metaInfo = {id: choiceDocId.value};
treeData.value = getTreeDataForTag(customRequestDoc.value, keyword, metaInfo);
treeData.value.unshift({key: 'info', title: '文档说明信息', isLeaf: true});
await nextTick();
expandedKeys.value = ['main'];
};
const toJsonObj = (value) => {
if (typeof value !== 'string') {
return value;
}
try {
return JSON.parse(value);
} catch (e) {
try {
// 处理变态的单双引号共存字符串
return eval('(' + value + ')');
} catch (e) {
return value || undefined;
}
}
};
return {
expandedKeys,
docChecked,
loadDoc,
loadTreeData,
treeData,
};
},
};
</script>
<style>
.doc-tree{padding: 10px 4px;}
.doc-tree .ant-tree-switcher{width: 15px;}
.doc-tree .ant-tree-switcher-noop{width: 0;}
.doc-tree .ant-tag{margin-right: 0;}
.ant-badge-not-a-wrapper:not(.ant-badge-status) {
vertical-align: text-top;
}
</style>

View File

@@ -90,7 +90,7 @@ let routers = [
]
},
{
path: '/api',
path: '/custom',
name: 'API请求',
meta: {
hidden: true,
@@ -98,9 +98,9 @@ let routers = [
component: EmptyKeepAliveLayout,
children: [
{
path: '/api/request',
path: '/custom/request',
name: '接口请求',
component: () => import('./views/apiRequest/ApiRequest.vue')
component: () => import('./views/customRequest/ApiRequest.vue')
},
]
},

View File

@@ -35,6 +35,9 @@ export default createStore({
openApiUrlMethodMap: {},
// 方法统计{post: 10, total: 20}
openApiMethodStatistic: {},
// 自建API原始文档
customRequestDoc: {},
}
},
getters: {
@@ -99,6 +102,10 @@ export default createStore({
setOpenApiMethodStatistic(state, openApiMethodStatistic) {
state.openApiMethodStatistic = openApiMethodStatistic;
},
// openApi
setCustomRequestDoc(state, customRequestDoc) {
state.customRequestDoc = customRequestDoc;
},
addTableName(state, item) {
let sameObj = Object.assign({}, state.pageTabNameMap);
sameObj[item.key] = item.val;

View File

@@ -50,7 +50,9 @@
<template v-if="column.dataIndex === 'docType'">
<a-tag color="red" v-if="text === 1">Swagger URL</a-tag>
<a-tag color="blue" v-else-if="text === 2">Swagger JSON</a-tag>
<a-tag color="blue" v-else-if="text === 3">Swagger URL</a-tag>
<a-tag color="green" v-else-if="text === 4">OpenApi JSON</a-tag>
<a-tag color="green" v-else-if="text === 5">自建API</a-tag>
</template>
<template v-if="column.dataIndex === 'openVisit'">
<a-tag color="pink" v-if="text === 0">未开放</a-tag>

View File

@@ -9,7 +9,7 @@
<a-radio :value="2">Swagger JSON</a-radio>
<a-radio :value="3">OpenApi URL</a-radio>
<a-radio :value="4">OpenApi JSON</a-radio>
<a-radio :value="5" disabled>自建API</a-radio>
<a-radio :value="5">自建API</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="文档地址" required name="docUrl" v-if="docEdit.docType === 1">