api文档支持开放访问
This commit is contained in:
@@ -13,5 +13,7 @@ export const zyplayerApi = {
|
||||
docApiGlobalParamList: data => apiClient({url: '/doc-api/global-param/list', method: 'post', data: data}),
|
||||
docApiGlobalParamUpdate: data => apiClient({url: '/doc-api/global-param/update', method: 'post', data: data}),
|
||||
requestUrl: data => apiClient({url: '/doc-api/proxy/request', method: 'post', data: data}),
|
||||
apiShareDocDetail: data => apiClient({url: '/doc-api/share/detail', method: 'post', data: data}),
|
||||
apiShareDocApisDetail: data => apiClient({url: '/doc-api/share/apis/detail', method: 'post', data: data}),
|
||||
};
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
@@ -2,7 +2,7 @@
|
||||
<a-layout class="api-menu-trigger">
|
||||
<a-layout-sider theme="light" :trigger="null" collapsible v-model:collapsed="appMenuCollapsed" :width="rightAsideWidth" style="height: 100vh;overflow: auto;">
|
||||
<div class="logo">
|
||||
<img src="../../assets/logo.png">
|
||||
<img src="../../assets/api-logo.png">
|
||||
<h1>API接口文档管理</h1>
|
||||
</div>
|
||||
<menu-layout :collapsed="appMenuCollapsed"></menu-layout>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {toRefs, ref, reactive, onMounted, watch} from 'vue';
|
||||
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';
|
||||
@@ -53,7 +53,7 @@
|
||||
let v2Doc = toJsonObj(res.data);
|
||||
// os:doc.swagger 和 doc.openapi 的区别
|
||||
if (typeof v2Doc !== 'object' || !v2Doc.openapi) {
|
||||
message.error('获取文档数据请求失败');
|
||||
message.error('获取文档数据失败,请检查文档是否为标准的OpenApi文档格式');
|
||||
return;
|
||||
}
|
||||
openApiDoc.value = v2Doc;
|
||||
@@ -72,10 +72,11 @@
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
const loadTreeData = () => {
|
||||
expandedKeys.value = ['main'];
|
||||
const loadTreeData = async () => {
|
||||
let metaInfo = {id: choiceDocId.value};
|
||||
treeData.value = getTreeDataForTag(openApiDoc.value, tagPathMap.value, searchKeywords.value, metaInfo);
|
||||
await nextTick();
|
||||
expandedKeys.value = ['main'];
|
||||
};
|
||||
const toJsonObj = (value) => {
|
||||
if (typeof value !== 'string') {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {toRefs, ref, reactive, onMounted, watch} from 'vue';
|
||||
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';
|
||||
@@ -53,7 +53,7 @@
|
||||
let v2Doc = toJsonObj(res.data);
|
||||
if (typeof v2Doc !== 'object' || !v2Doc.swagger) {
|
||||
callback(false);
|
||||
message.error('获取文档数据请求失败');
|
||||
message.error('获取文档数据失败,请检查文档是否为标准的Swagger文档格式');
|
||||
return;
|
||||
}
|
||||
swaggerDoc.value = v2Doc;
|
||||
@@ -72,10 +72,11 @@
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
const loadTreeData = () => {
|
||||
expandedKeys.value = ['main'];
|
||||
const loadTreeData = async () => {
|
||||
let metaInfo = {id: choiceDocId.value};
|
||||
treeData.value = getTreeDataForTag(swaggerDoc.value, tagPathMap.value, searchKeywords.value, metaInfo);
|
||||
await nextTick();
|
||||
expandedKeys.value = ['main'];
|
||||
};
|
||||
const toJsonObj = (value) => {
|
||||
if (typeof value !== 'string') {
|
||||
|
||||
154
zyplayer-doc-ui/api-ui/src/components/share/GlobalLayout.vue
Normal file
154
zyplayer-doc-ui/api-ui/src/components/share/GlobalLayout.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<a-layout class="api-menu-trigger">
|
||||
<a-layout-sider theme="light" :trigger="null" collapsible v-model:collapsed="appMenuCollapsed" :width="rightAsideWidth" style="height: 100vh;overflow: auto;">
|
||||
<div class="logo">
|
||||
<img src="../../assets/api-logo.png">
|
||||
<h1>API开放文档</h1>
|
||||
</div>
|
||||
<menu-layout :collapsed="appMenuCollapsed"></menu-layout>
|
||||
</a-layout-sider>
|
||||
<div ref="rightResize" class="right-resize" v-show="!appMenuCollapsed">
|
||||
<i ref="rightResizeBar">...</i>
|
||||
</div>
|
||||
<a-layout>
|
||||
<a-layout-header style="border-bottom: 2px solid #eee;background: #fff; padding: 0; box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);-webkit-box-shadow:0 1px 4px rgba(0, 21, 41, 0.08);">
|
||||
<a-row type="flex">
|
||||
<a-col flex="auto">
|
||||
<MenuUnfoldOutlined class="trigger" v-if="appMenuCollapsed" @click="appMenuCollapsed = !appMenuCollapsed"/>
|
||||
<MenuFoldOutlined class="trigger" v-else @click="appMenuCollapsed = !appMenuCollapsed"/>
|
||||
</a-col>
|
||||
<a-col flex="400px" style="text-align: right;padding-right: 20px;">
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-layout-header>
|
||||
<a-layout-content style="height: calc(100vh - 80px);overflow: auto;background: #fff;">
|
||||
<router-view></router-view>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MenuLayout from './MenuLayout.vue'
|
||||
import {BarChartOutlined, MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons-vue';
|
||||
|
||||
const minHeight = window.innerHeight - 64 - 122;
|
||||
export default {
|
||||
components: {MenuLayout, BarChartOutlined, MenuFoldOutlined, MenuUnfoldOutlined},
|
||||
data() {
|
||||
return {
|
||||
minHeight: minHeight + 'px',
|
||||
appMenuCollapsed: false,
|
||||
rightAsideWidth: 300
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.dragChangeRightAsideWidth();
|
||||
},
|
||||
methods: {
|
||||
dragChangeRightAsideWidth: function() {
|
||||
// 保留this引用
|
||||
let resize = this.$refs.rightResize;
|
||||
let resizeBar = this.$refs.rightResizeBar;
|
||||
resize.onmousedown = e => {
|
||||
let startX = e.clientX;
|
||||
// 颜色改变提醒
|
||||
resize.style.background = "#ccc";
|
||||
resizeBar.style.background = "#aaa";
|
||||
resize.left = resize.offsetLeft;
|
||||
document.onmousemove = e2 => {
|
||||
// 计算并应用位移量
|
||||
let endX = e2.clientX;
|
||||
let moveLen = startX - endX;
|
||||
if ((moveLen < 0 && this.rightAsideWidth < 600) || (moveLen > 0 && this.rightAsideWidth > 280)) {
|
||||
startX = endX;
|
||||
this.rightAsideWidth -= moveLen;
|
||||
if (this.rightAsideWidth < 280) {
|
||||
this.rightAsideWidth = 280;
|
||||
}
|
||||
}
|
||||
};
|
||||
document.onmouseup = () => {
|
||||
// 颜色恢复
|
||||
resize.style.background = "#fafafa";
|
||||
resizeBar.style.background = "#ccc";
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trigger {
|
||||
font-size: 20px;
|
||||
line-height: 64px;
|
||||
padding: 0 24px;
|
||||
cursor: pointer;
|
||||
transition: color .3s;
|
||||
}
|
||||
|
||||
.trigger:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 64px;
|
||||
position: relative;
|
||||
line-height: 64px;
|
||||
padding-left: 24px;
|
||||
-webkit-transition: all .3s;
|
||||
transition: all .3s;
|
||||
overflow: hidden;
|
||||
background: #1d4e89;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
margin: 0 0 0 12px;
|
||||
font-family: "Myriad Pro", "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 32px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.api-menu-trigger {
|
||||
min-height: 100%;
|
||||
}
|
||||
.right-resize {
|
||||
width: 5px;
|
||||
cursor: w-resize;
|
||||
background: #fafafa;
|
||||
}
|
||||
.right-resize i{
|
||||
margin-top: 300px;
|
||||
width: 5px;
|
||||
height: 35px;
|
||||
display: inline-block;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
line-height: 8px;
|
||||
border-radius: 5px;
|
||||
background: #ccc;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.ant-layout-sider {
|
||||
transition: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<template class="menu-layout-children" v-if="!menuItem.meta || !menuItem.meta.hidden">
|
||||
<template v-if="!!menuItem.children">
|
||||
<a-sub-menu :key="menuItem.path" v-if="haveShowChildren(menuItem.children)">
|
||||
<template #title>
|
||||
<template v-if="menuItem.meta">
|
||||
<SettingOutlined v-if="menuItem.meta.icon === 'SettingOutlined'"/>
|
||||
<FileTextOutlined v-if="menuItem.meta.icon === 'FileTextOutlined'"/>
|
||||
</template>
|
||||
<span>{{menuItem.name}}</span>
|
||||
</template>
|
||||
<MenuLayoutChildren :menuItem="children" v-for="children in menuItem.children"></MenuLayoutChildren>
|
||||
</a-sub-menu>
|
||||
</template>
|
||||
<a-menu-item :key="menuItem.path" v-else>
|
||||
<router-link :to="{path: menuItem.path, query: menuItem.query}">
|
||||
<template v-if="menuItem.meta">
|
||||
<DashboardOutlined v-if="menuItem.meta.icon === 'DashboardOutlined'"/>
|
||||
<FileTextOutlined v-if="menuItem.meta.icon === 'FileTextOutlined'"/>
|
||||
<InfoCircleOutlined v-if="menuItem.meta.icon === 'InfoCircleOutlined'"/>
|
||||
</template>
|
||||
<span>{{menuItem.name}}</span>
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
StarOutlined,
|
||||
SettingOutlined,
|
||||
CarryOutOutlined,
|
||||
FileTextOutlined,
|
||||
DashboardOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'MenuLayoutChildren',
|
||||
props: {
|
||||
menuItem: Object,
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
components: {
|
||||
StarOutlined, SettingOutlined, CarryOutOutlined, FileTextOutlined,
|
||||
DashboardOutlined, InfoCircleOutlined
|
||||
},
|
||||
methods: {
|
||||
haveShowChildren(children) {
|
||||
return children.filter(item => (!item.meta || !item.meta.hidden)).length > 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
139
zyplayer-doc-ui/api-ui/src/components/share/MenuLayout.vue
Normal file
139
zyplayer-doc-ui/api-ui/src/components/share/MenuLayout.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="menu-layout">
|
||||
<a-menu theme="light" mode="inline" :inline-collapsed="false" v-model:openKeys="openKeys" v-model:selectedKeys="selectedKeys">
|
||||
<menu-children-layout :menuItem="menuItem" v-for="menuItem in menuData"></menu-children-layout>
|
||||
</a-menu>
|
||||
<a-divider style="margin: 6px 0;"/>
|
||||
<div v-show="!collapsed" class="doc-tree">
|
||||
<a-spin tip="加载中..." :spinning="treeDataLoading">
|
||||
<div style="margin-bottom: 10px;">
|
||||
<a-input-search v-model:value="searchKeywords" placeholder="搜索文档内容" style="width: 100%;margin-top: 10px;" @search="docSearch"/>
|
||||
</div>
|
||||
<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>
|
||||
</template>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</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 MenuChildrenLayout from './MenuChildrenLayout.vue'
|
||||
import {zyplayerApi} from '../../api'
|
||||
import DocTreeSwagger from './doc-tree/Swagger.vue'
|
||||
import DocTreeOpenApi from './doc-tree/OpenApi.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
components: {MenuChildrenLayout, DocTreeSwagger, DocTreeOpenApi},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
let menuData = ref([]);
|
||||
let selectedKeys = ref([]);
|
||||
let openKeys = ref([]);
|
||||
// 文档;
|
||||
let treeDataLoading = ref(false);
|
||||
let docResourceList = ref([]);
|
||||
let docChoiceId = ref();
|
||||
let searchKeywords = ref('');
|
||||
let docChoice = ref({});
|
||||
|
||||
const getApiDocList = () => {
|
||||
zyplayerApi.apiShareDocDetail({shareUuid: docChoiceId.value}).then(res => {
|
||||
docChoice.value = res.data || {};
|
||||
store.commit('setApiDoc', docChoice.value);
|
||||
loadDoc();
|
||||
});
|
||||
};
|
||||
let swaggerRef = ref();
|
||||
let openApiRef = ref();
|
||||
const loadDoc = async () => {
|
||||
treeDataLoading.value = true;
|
||||
await nextTick();
|
||||
const loadDocCallback = () => {
|
||||
treeDataLoading.value = false;
|
||||
};
|
||||
// 如果文档是swagger类型
|
||||
if (docChoice.value.docType === 1 || docChoice.value.docType === 2) {
|
||||
if (swaggerRef.value) {
|
||||
swaggerRef.value.loadDoc(docChoiceId.value, searchKeywords.value, loadDocCallback);
|
||||
}
|
||||
} else if (docChoice.value.docType === 3 || docChoice.value.docType === 4) {
|
||||
if (openApiRef.value) {
|
||||
openApiRef.value.loadDoc(docChoiceId.value, searchKeywords.value, loadDocCallback);
|
||||
}
|
||||
}
|
||||
};
|
||||
const docSearch = () => {
|
||||
loadDoc();
|
||||
};
|
||||
onMounted(() => {
|
||||
docChoiceId.value = route.query.uuid;
|
||||
if (!docChoiceId.value) {
|
||||
message.error('访问的开放文档参数错误');
|
||||
return;
|
||||
}
|
||||
// 左侧菜单处理
|
||||
// menuData.value = router.options.routes.find((item) => item.path === '/share').children[0].children;
|
||||
menuData.value = [
|
||||
{
|
||||
path: '/share/home',
|
||||
name: '开放文档使用说明',
|
||||
meta: {
|
||||
icon: 'FileTextOutlined'
|
||||
},
|
||||
query: {uuid: docChoiceId.value},
|
||||
}
|
||||
];
|
||||
let meta = route.meta || {};
|
||||
let path = route.path;
|
||||
if (!!meta.parentPath) {
|
||||
path = meta.parentPath;
|
||||
}
|
||||
selectedKeys.value = [path];
|
||||
let matched = route.matched;
|
||||
if (matched.length >= 1) {
|
||||
openKeys.value = [matched[1].path];
|
||||
}
|
||||
getApiDocList();
|
||||
});
|
||||
|
||||
return {
|
||||
menuData,
|
||||
selectedKeys,
|
||||
openKeys,
|
||||
treeDataLoading,
|
||||
docResourceList,
|
||||
docChoiceId,
|
||||
searchKeywords,
|
||||
swaggerRef,
|
||||
openApiRef,
|
||||
docChoice,
|
||||
docSearch,
|
||||
};
|
||||
},
|
||||
};
|
||||
</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>
|
||||
97
zyplayer-doc-ui/api-ui/src/components/share/PageLayout.vue
Normal file
97
zyplayer-doc-ui/api-ui/src/components/share/PageLayout.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div class="page-layout">
|
||||
<a-tabs type="editable-card" hide-add v-model:activeKey="activePage" @tab-click="changePage" @edit="editPageTab" style="padding: 5px 10px 0;">
|
||||
<a-tab-pane closable :tab="pageTabNameMap[item.fullPath]||item.name" :name="getRouteRealPath(item)" :fullPath="item.fullPath" :key="item.fullPath" v-for="item in pageList"/>
|
||||
</a-tabs>
|
||||
<div class="page-body">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<keep-alive>
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageTableView',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
pageList: [],
|
||||
linkList: [],
|
||||
activePage: '',
|
||||
multiPage: true,
|
||||
ignoreParamPath: [
|
||||
],
|
||||
apiRequestIndex: 1,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pageTabNameMap () {
|
||||
return this.$store.state.pageTabNameMap;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
let {name, path, fullPath} = this.$route;
|
||||
this.pageList.push({name, path, fullPath});
|
||||
let activePage = this.getRouteRealPath(this.$route);
|
||||
this.linkList.push(activePage);
|
||||
this.activePage = activePage;
|
||||
this.$router.push(this.$route.fullPath);
|
||||
},
|
||||
watch: {
|
||||
'$route': function (newRoute, oldRoute) {
|
||||
let activePage = this.getRouteRealPath(newRoute);
|
||||
this.activePage = activePage;
|
||||
if (this.linkList.indexOf(activePage) < 0) {
|
||||
this.linkList.push(activePage);
|
||||
let {name, path, fullPath} = newRoute;
|
||||
this.pageList.push({name, path, fullPath});
|
||||
}
|
||||
let pageRoute = this.pageList.find(item => this.getRouteRealPath(item) === activePage);
|
||||
pageRoute.fullPath = newRoute.fullPath;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isIgnoreParamPath(path) {
|
||||
return this.ignoreParamPath.indexOf(path) >= 0;
|
||||
},
|
||||
getRouteRealPath(route) {
|
||||
return this.isIgnoreParamPath(route.path) ? route.path : route.fullPath;
|
||||
},
|
||||
changePage(tab) {
|
||||
let checkedTab = this.pageList.find(item => item.fullPath === tab);
|
||||
this.activePage = this.getRouteRealPath(checkedTab);
|
||||
this.$router.push(checkedTab.fullPath);
|
||||
},
|
||||
editPage(key, action) {
|
||||
this[action](key);
|
||||
},
|
||||
editPageTab(key, action) {
|
||||
this.removePageTab(key);
|
||||
},
|
||||
removePageTab(key) {
|
||||
if (this.pageList.length === 1) {
|
||||
this.$message.warning('这是最后一页,不能再关闭了啦');
|
||||
return;
|
||||
}
|
||||
this.pageList = this.pageList.filter(item => this.getRouteRealPath(item) !== key);
|
||||
this.linkList = this.linkList.filter(item => item !== key);
|
||||
let index = this.linkList.indexOf(this.activePage);
|
||||
if (index < 0) {
|
||||
index = this.linkList.length - 1;
|
||||
this.activePage = this.linkList[index];
|
||||
this.$router.push(this.activePage);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page-layout{background: #fff;}
|
||||
.page-layout .page-body{padding: 0 10px 10px 10px;}
|
||||
.ant-tabs-bar{margin-bottom: 0;}
|
||||
</style>
|
||||
106
zyplayer-doc-ui/api-ui/src/components/share/doc-tree/OpenApi.vue
Normal file
106
zyplayer-doc-ui/api-ui/src/components/share/doc-tree/OpenApi.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<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="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>
|
||||
<a-badge v-if="children" :count="children.length" :number-style="{backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset'}"/>
|
||||
</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 {zyplayerApi} from '../../../api'
|
||||
import {analysisOpenApiData, getTreeDataForTag} from '../../../assets/core/OpenApiTreeAnalysis.js'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
let tagPathMap = ref({});
|
||||
let openApiDoc = ref({});
|
||||
let treeData = ref([]);
|
||||
let expandedKeys = ref([]);
|
||||
let choiceDocId = ref('');
|
||||
let searchKeywords = ref('');
|
||||
|
||||
const docChecked = (val, node) => {
|
||||
if (node.node.isLeaf) {
|
||||
let dataRef = node.node.dataRef;
|
||||
router.push({path: '/share/openapi/view', query: dataRef.query});
|
||||
}
|
||||
};
|
||||
const loadDoc = (docId, keyword, callback) => {
|
||||
choiceDocId.value = docId;
|
||||
searchKeywords.value = keyword;
|
||||
zyplayerApi.apiShareDocApisDetail({shareUuid: docId}).then(res => {
|
||||
let v2Doc = toJsonObj(res.data);
|
||||
// os:doc.swagger 和 doc.openapi 的区别
|
||||
if (typeof v2Doc !== 'object' || !v2Doc.openapi) {
|
||||
message.error('获取文档数据失败,请检查文档是否为标准的OpenApi文档格式');
|
||||
return;
|
||||
}
|
||||
openApiDoc.value = v2Doc;
|
||||
store.commit('setOpenApiDoc', v2Doc);
|
||||
let treeData = analysisOpenApiData(v2Doc);
|
||||
store.commit('setOpenApiUrlMethodMap', treeData.urlMethodMap);
|
||||
store.commit('setOpenApiMethodStatistic', treeData.methodStatistic);
|
||||
tagPathMap.value = treeData.tagPathMap;
|
||||
loadTreeData();
|
||||
callback();
|
||||
});
|
||||
};
|
||||
const loadTreeData = async () => {
|
||||
let metaInfo = {uuid: choiceDocId.value};
|
||||
treeData.value = getTreeDataForTag(openApiDoc.value, tagPathMap.value, searchKeywords.value, metaInfo);
|
||||
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,
|
||||
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>
|
||||
106
zyplayer-doc-ui/api-ui/src/components/share/doc-tree/Swagger.vue
Normal file
106
zyplayer-doc-ui/api-ui/src/components/share/doc-tree/Swagger.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<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="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>
|
||||
<a-badge v-if="children" :count="children.length" :number-style="{backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset'}"/>
|
||||
</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 {zyplayerApi} from '../../../api'
|
||||
import {analysisSwaggerData, getTreeDataForTag} from '../../../assets/core/SwaggerTreeAnalysis.js'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
let tagPathMap = ref({});
|
||||
let swaggerDoc = ref({});
|
||||
let treeData = ref([]);
|
||||
let expandedKeys = ref([]);
|
||||
let choiceDocId = ref('');
|
||||
let searchKeywords = ref('');
|
||||
|
||||
const docChecked = (val, node) => {
|
||||
if (node.node.isLeaf) {
|
||||
let dataRef = node.node.dataRef;
|
||||
router.push({path: '/share/swagger/view', query: dataRef.query});
|
||||
}
|
||||
};
|
||||
const loadDoc = (docId, keyword, callback) => {
|
||||
choiceDocId.value = docId;
|
||||
searchKeywords.value = keyword;
|
||||
zyplayerApi.apiShareDocApisDetail({shareUuid: docId}).then(res => {
|
||||
let v2Doc = toJsonObj(res.data);
|
||||
if (typeof v2Doc !== 'object' || !v2Doc.swagger) {
|
||||
callback(false);
|
||||
message.error('获取文档数据失败,请检查文档是否为标准的Swagger文档格式');
|
||||
return;
|
||||
}
|
||||
swaggerDoc.value = v2Doc;
|
||||
store.commit('setSwaggerDoc', v2Doc);
|
||||
let treeData = analysisSwaggerData(v2Doc);
|
||||
store.commit('setSwaggerUrlMethodMap', treeData.urlMethodMap);
|
||||
store.commit('setSwaggerMethodStatistic', treeData.methodStatistic);
|
||||
tagPathMap.value = treeData.tagPathMap;
|
||||
loadTreeData();
|
||||
callback(true);
|
||||
});
|
||||
};
|
||||
const loadTreeData = async () => {
|
||||
let metaInfo = {uuid: choiceDocId.value};
|
||||
treeData.value = getTreeDataForTag(swaggerDoc.value, tagPathMap.value, searchKeywords.value, metaInfo);
|
||||
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,
|
||||
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>
|
||||
@@ -1,4 +1,5 @@
|
||||
import PageLayout from './components/layouts/PageLayout.vue'
|
||||
import SharePageLayout from './components/share/PageLayout.vue'
|
||||
import EmptyKeepAliveLayout from './components/layouts/EmptyKeepAliveLayout.vue'
|
||||
|
||||
let routers = [
|
||||
@@ -56,12 +57,12 @@ let routers = [
|
||||
children: [
|
||||
{
|
||||
path: '/swagger/info',
|
||||
name: 'swagger文档信息',
|
||||
name: 'Swagger文档信息',
|
||||
component: () => import('./views/swagger/DocInfo.vue')
|
||||
},
|
||||
{
|
||||
path: '/swagger/view',
|
||||
name: 'swagger文档展示',
|
||||
name: 'Swagger文档展示',
|
||||
component: () => import('./views/swagger/DocView.vue')
|
||||
},
|
||||
]
|
||||
@@ -78,12 +79,12 @@ let routers = [
|
||||
children: [
|
||||
{
|
||||
path: '/openapi/info',
|
||||
name: 'openApi文档信息',
|
||||
name: 'OpenApi文档信息',
|
||||
component: () => import('./views/openapi/DocInfo.vue')
|
||||
},
|
||||
{
|
||||
path: '/openapi/view',
|
||||
name: 'openApi文档展示',
|
||||
name: 'OpenApi文档展示',
|
||||
component: () => import('./views/openapi/DocView.vue')
|
||||
},
|
||||
]
|
||||
@@ -107,5 +108,47 @@ let routers = [
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/share',
|
||||
name: '开放文档',
|
||||
component: () => import('./components/share/GlobalLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/doc',
|
||||
name: '开放文档管理',
|
||||
component: SharePageLayout,
|
||||
children: [
|
||||
{
|
||||
path: '/share/home',
|
||||
name: '开放文档使用说明',
|
||||
meta: {
|
||||
icon: 'FileTextOutlined'
|
||||
},
|
||||
component: () => import('./views/share/ShareHome.vue')
|
||||
},
|
||||
{
|
||||
path: '/doc',
|
||||
name: '开放文档查看',
|
||||
meta: {
|
||||
hidden: true,
|
||||
},
|
||||
component: EmptyKeepAliveLayout,
|
||||
children: [
|
||||
{
|
||||
path: '/share/swagger/view',
|
||||
name: 'Swagger开放文档展示',
|
||||
component: () => import('./views/swagger/share/DocView.vue')
|
||||
},
|
||||
{
|
||||
path: '/share/openapi/view',
|
||||
name: 'OpenApi开放文档展示',
|
||||
component: () => import('./views/openapi/share/DocView.vue')
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
export default routers;
|
||||
|
||||
@@ -32,10 +32,19 @@
|
||||
:scroll="{ x: 1400, y: 'calc(100vh - 300px)' }">
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a-button type="link" @click="editDoc(record)">编辑</a-button>
|
||||
<a-button size="small" type="link" @click="editDoc(record)">编辑</a-button>
|
||||
<a-popconfirm title="确定要删除吗?" @confirm="deleteDoc(record)">
|
||||
<a-button type="link" danger>删除</a-button>
|
||||
<a-button size="small" type="link" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<template #overlay>
|
||||
<a-menu @click="handleActionMenuClick($event, record)">
|
||||
<a-menu-item key="shareView"><link-outlined /> 查看开放文档</a-menu-item>
|
||||
<a-menu-item key="shareInstruction"><edit-outlined /> 编辑开放文档说明</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button type="link" size="small">更多<DownOutlined /></a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'docType'">
|
||||
<a-tag color="red" v-if="text === 1">Swagger URL</a-tag>
|
||||
@@ -81,7 +90,9 @@
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label="文档内容" required name="jsonContent" v-else-if="docEdit.docType === 2">
|
||||
<a-textarea placeholder="请输入JSON格式的swagger文档内容" v-model:value="docEdit.jsonContent"></a-textarea>
|
||||
<!-- 尝试使用ace编辑器,但感觉除了卡顿意义不大,不会在这里编辑json内容,暂时用textarea代替-->
|
||||
<!-- <ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>-->
|
||||
<a-textarea placeholder="请输入JSON格式的Swagger文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>
|
||||
<template #extra>
|
||||
查看文档内容
|
||||
<a-popover title="文档内容说明">
|
||||
@@ -108,7 +119,8 @@
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label="文档内容" required name="jsonContent" v-else-if="docEdit.docType === 4">
|
||||
<a-textarea placeholder="请输入JSON格式的openapi文档内容" v-model:value="docEdit.jsonContent"></a-textarea>
|
||||
<!-- <ace-editor v-model:value="docEdit.jsonContent" lang="json" theme="monokai" width="100%" height="100" :options="aceEditorConfig"></ace-editor>-->
|
||||
<a-textarea placeholder="请输入JSON格式的OpenApi文档内容" v-model:value="docEdit.jsonContent" :auto-size="{ minRows: 5, maxRows: 10 }"></a-textarea>
|
||||
<template #extra>
|
||||
查看文档内容
|
||||
<a-popover title="文档内容说明">
|
||||
@@ -135,12 +147,15 @@
|
||||
</a-popover>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="开放访问" required name="openVisit">-->
|
||||
<!-- <a-radio-group v-model:value="docEdit.openVisit">-->
|
||||
<!-- <a-radio :value="0">否</a-radio>-->
|
||||
<!-- <a-radio :value="1">开放</a-radio>-->
|
||||
<!-- </a-radio-group>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="开放访问" required name="openVisit">
|
||||
<a-radio-group v-model:value="docEdit.openVisit">
|
||||
<a-radio :value="0">否</a-radio>
|
||||
<a-radio :value="1">开放</a-radio>
|
||||
</a-radio-group>
|
||||
<template #extra>
|
||||
开放访问后无需登录即可通过<a @click="openShareViewWindow(docEdit)">开放文档URL</a>访问改文档信息
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" required name="docStatus">
|
||||
<a-radio-group v-model:value="docEdit.docStatus">
|
||||
<a-radio :value="1">启用</a-radio>
|
||||
@@ -149,14 +164,21 @@
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<EditShareInstruction ref="instruction"></EditShareInstruction>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toRefs, ref, reactive, onMounted } from 'vue';
|
||||
import {zyplayerApi} from '../../api';
|
||||
import {useStore} from 'vuex';
|
||||
import aceEditor from "../../assets/ace-editor";
|
||||
import EditShareInstruction from "./components/EditShareInstruction.vue";
|
||||
import {getZyplayerApiBaseUrl} from "../../api/request/utils";
|
||||
import {DownOutlined, LinkOutlined, EditOutlined} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
components: {aceEditor, EditShareInstruction, DownOutlined, LinkOutlined, EditOutlined},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
let docList = ref([]);
|
||||
@@ -218,6 +240,25 @@
|
||||
});
|
||||
};
|
||||
const deleteDoc = async (row) => updateDoc(row.id, null, 0);
|
||||
|
||||
const openShareViewWindow = record => {
|
||||
if (!record.shareUuid) {
|
||||
message.warning('请先保存文档后再试');
|
||||
} else if (record.openVisit === 1) {
|
||||
window.open(getZyplayerApiBaseUrl() + '/doc-api#/share/home?uuid=' + record.shareUuid);
|
||||
} else {
|
||||
message.warning('改文档尚未开启开放访问功能,请在编辑页选择开放后再试');
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionMenuClick = (item, record) => {
|
||||
if (item.key === 'shareView') {
|
||||
openShareViewWindow(record);
|
||||
} else if (item.key === 'shareInstruction') {
|
||||
instruction.value.editDoc(record.id);
|
||||
}
|
||||
}
|
||||
let instruction = ref();
|
||||
onMounted(() => {
|
||||
searchDocList();
|
||||
});
|
||||
@@ -234,7 +275,10 @@
|
||||
deleteDoc,
|
||||
editDoc,
|
||||
handleTableChange,
|
||||
openShareViewWindow,
|
||||
handleActionMenuClick,
|
||||
pagination,
|
||||
instruction,
|
||||
newDocRules: {
|
||||
name: [{required: true, message: '请输入文档名称', trigger: 'change'}],
|
||||
docUrl: [{required: true, message: '请输入文档地址', trigger: 'change'}],
|
||||
@@ -247,12 +291,21 @@
|
||||
{title: 'ID', dataIndex: 'id', width: 70},
|
||||
{title: '文档名称', dataIndex: 'name', width: 250},
|
||||
{title: '文档类型', dataIndex: 'docType', width: 120},
|
||||
// {title: '开放访问', dataIndex: 'openVisit', width: 90},
|
||||
{title: '开放访问', dataIndex: 'openVisit', width: 90},
|
||||
{title: '状态', dataIndex: 'docStatus', width: 90},
|
||||
{title: '文档地址', dataIndex: 'docUrl'},
|
||||
{title: '目标域名', dataIndex: 'rewriteDomain', width: 250},
|
||||
{title: '操作', dataIndex: 'operation', fixed: 'right', width: 170},
|
||||
{title: '操作', dataIndex: 'operation', fixed: 'right', width: 200},
|
||||
],
|
||||
aceEditorConfig: {
|
||||
wrap: true,
|
||||
autoScrollEditorIntoView: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: true,
|
||||
minLines: 10,
|
||||
maxLines: 15,
|
||||
},
|
||||
swaggerDocDemo:
|
||||
'{\n'
|
||||
+ ' "swagger": "2.0",\n'
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="editShareInstructionVisible" @ok="handleNewDocOk" width="90%"
|
||||
:bodyStyle="{height: 'calc(100vh - 300px)'}">
|
||||
<template #title>
|
||||
编辑开放文档说明
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>此说明文档将展示在开放文档的首页展示,可点击‘查看开放文档’查看效果</template>
|
||||
<info-circle-outlined />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<mavon-editor ref="mavonEditor" v-model="shareInstruction" :toolbars="toolbars"
|
||||
:externalLink="false"
|
||||
style="height: 100%;"
|
||||
placeholder="请录入文档内容"/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toRefs, ref, reactive, onMounted, nextTick } from 'vue';
|
||||
import {zyplayerApi} from '../../../api';
|
||||
import {useStore} from 'vuex';
|
||||
import aceEditor from "../../../assets/ace-editor";
|
||||
import {getZyplayerApiBaseUrl} from "../../../api/request/utils";
|
||||
import {BranchesOutlined, InfoCircleOutlined} from '@ant-design/icons-vue';
|
||||
import {mavonEditor, markdownIt} from 'mavon-editor'
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
components: {aceEditor, BranchesOutlined, mavonEditor, InfoCircleOutlined},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
let docEdit = ref({});
|
||||
let shareInstruction = ref('');
|
||||
let editShareInstructionVisible = ref(false);
|
||||
const handleNewDocOk = async () => {
|
||||
if (!shareInstruction.value) {
|
||||
message.error('请输入开放文档的说明');
|
||||
return;
|
||||
}
|
||||
zyplayerApi.apiDocUpdate({id: docEdit.value.id, shareInstruction: shareInstruction.value}).then(res => {
|
||||
editShareInstructionVisible.value = false;
|
||||
});
|
||||
};
|
||||
const editDoc = async (id) => {
|
||||
editShareInstructionVisible.value = true;
|
||||
zyplayerApi.apiDocDetail({id: id}).then(res => {
|
||||
docEdit.value = res.data;
|
||||
shareInstruction.value = res.data.shareInstruction;
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
});
|
||||
return {
|
||||
editShareInstructionVisible,
|
||||
docEdit,
|
||||
shareInstruction,
|
||||
handleNewDocOk,
|
||||
editDoc,
|
||||
toolbars: {
|
||||
bold: true, // 粗体
|
||||
italic: true, // 斜体
|
||||
header: true, // 标题
|
||||
underline: true, // 下划线
|
||||
strikethrough: true, // 中划线
|
||||
mark: true, // 标记
|
||||
superscript: true, // 上角标
|
||||
subscript: true, // 下角标
|
||||
quote: true, // 引用
|
||||
ol: true, // 有序列表
|
||||
ul: true, // 无序列表
|
||||
link: true, // 链接
|
||||
imagelink: true, // 图片链接
|
||||
code: true, // code
|
||||
table: true, // 表格
|
||||
fullscreen: true, // 全屏编辑
|
||||
readmodel: true, // 沉浸式阅读
|
||||
/* 1.3.5 */
|
||||
undo: true, // 上一步
|
||||
redo: true, // 下一步
|
||||
trash: true, // 清空
|
||||
save: true, // 保存(触发events中的save事件)
|
||||
/* 1.4.2 */
|
||||
navigation: true, // 导航目录
|
||||
/* 2.1.8 */
|
||||
alignleft: true, // 左对齐
|
||||
aligncenter: true, // 居中
|
||||
alignright: true, // 右对齐
|
||||
/* 2.2.1 */
|
||||
subfield: true, // 单双栏模式
|
||||
preview: true, // 预览
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
101
zyplayer-doc-ui/api-ui/src/views/openapi/share/DocView.vue
Normal file
101
zyplayer-doc-ui/api-ui/src/views/openapi/share/DocView.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<template v-if="isLoadSuccess">
|
||||
<DocContent :docInfoShow="docInfoShow" :requestParamList="requestParamList" :responseParamList="responseParamList"></DocContent>
|
||||
</template>
|
||||
<a-spin v-else tip="文档数据加载中...">
|
||||
<div style="padding: 20px 0;height: 100px;"></div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {toRefs, ref, reactive, onMounted, watch} from 'vue';
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import {useStore} from 'vuex';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
|
||||
import openApiAnalysis from '../../../assets/core/OpenApiAnalysis.js'
|
||||
import DocContent from '../docView/DocContent.vue'
|
||||
import {markdownIt} from 'mavon-editor'
|
||||
import 'mavon-editor/dist/markdown/github-markdown.min.css'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
|
||||
export default {
|
||||
components: { DocContent },
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
let activePage = ref('doc');
|
||||
let requestParamList = ref([]);
|
||||
let responseParamList = ref([]);
|
||||
let docInfoShow = ref({
|
||||
url: '',
|
||||
description: '',
|
||||
method: '',
|
||||
consumes: '',
|
||||
produces: '',
|
||||
});
|
||||
let isLoadSuccess = ref(false);
|
||||
let intervalNum = 0;
|
||||
let intervalTimer = undefined;
|
||||
const initLoadDocument = () => {
|
||||
let path = route.query.path + '.' + route.query.method;
|
||||
if (Object.keys(store.state.openApiUrlMethodMap).length <= 0) {
|
||||
console.log('文档尚未加载,等待加载完成');
|
||||
if (!intervalTimer) {
|
||||
intervalTimer = setInterval(() => {
|
||||
if (isLoadSuccess.value || intervalNum++ > 50) {
|
||||
clearInterval(intervalTimer);
|
||||
return;
|
||||
}
|
||||
if (Object.keys(store.state.openApiUrlMethodMap).length > 0) {
|
||||
console.log('文档内容改变,重新加载文档');
|
||||
initLoadDocument();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let docInfo = store.state.openApiUrlMethodMap[path];
|
||||
if (!docInfo) {
|
||||
message.error('没有找到对应的文档');
|
||||
return;
|
||||
}
|
||||
isLoadSuccess.value = true;
|
||||
store.commit('addTableName', {key: route.fullPath, val: docInfo.summary});
|
||||
// 解析接口说明
|
||||
let consumes = '', produces = '';
|
||||
if (docInfo.consumes && docInfo.consumes.length > 0) {
|
||||
consumes = docInfo.consumes.join(' ');
|
||||
}
|
||||
if (docInfo.produces && docInfo.produces.length > 0) {
|
||||
produces = docInfo.produces.join(' ');
|
||||
}
|
||||
let description = markdownIt.render(docInfo.description || docInfo.summary || '');
|
||||
docInfoShow.value = {
|
||||
url: docInfo.url,
|
||||
description: description,
|
||||
method: docInfo.method || '',
|
||||
consumes: consumes,
|
||||
produces: produces,
|
||||
};
|
||||
// 解析请求参数
|
||||
let definitionsDataMap = store.state.openApiDefinitions;
|
||||
requestParamList.value = openApiAnalysis.getRequestParamList(docInfo.parameters, definitionsDataMap);
|
||||
responseParamList.value = openApiAnalysis.getResponseParamList(docInfo.responses, definitionsDataMap);
|
||||
}
|
||||
onMounted(() => {
|
||||
initLoadDocument();
|
||||
});
|
||||
const changePage = () => {
|
||||
}
|
||||
return {
|
||||
docInfoShow,
|
||||
activePage,
|
||||
changePage,
|
||||
isLoadSuccess,
|
||||
requestParamList,
|
||||
responseParamList,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
34
zyplayer-doc-ui/api-ui/src/views/share/ShareHome.vue
Normal file
34
zyplayer-doc-ui/api-ui/src/views/share/ShareHome.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div v-if="apiDoc.shareInstruction">
|
||||
<div class="markdown-body" v-html="markdownToHtml(apiDoc.shareInstruction)" style="margin: 0 auto;max-width: 1000px;"></div>
|
||||
</div>
|
||||
<div v-else style="text-align: center;">欢迎访问开放API文档</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {toRefs, ref, reactive, onMounted, watch, computed} from 'vue';
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import {useStore} from 'vuex';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
|
||||
import {markdownIt} from 'mavon-editor'
|
||||
import 'mavon-editor/dist/markdown/github-markdown.min.css'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
import swaggerAnalysis from "../../assets/core/SwaggerAnalysis";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const apiDoc = computed(() => store.state.apiDoc);
|
||||
onMounted(() => {
|
||||
});
|
||||
const markdownToHtml = desc => {
|
||||
return markdownIt.render(desc || '');
|
||||
}
|
||||
return {
|
||||
apiDoc,
|
||||
markdownToHtml,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
101
zyplayer-doc-ui/api-ui/src/views/swagger/share/DocView.vue
Normal file
101
zyplayer-doc-ui/api-ui/src/views/swagger/share/DocView.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<template v-if="isLoadSuccess">
|
||||
<DocContent :docInfoShow="docInfoShow" :requestParamList="requestParamList" :responseParamList="responseParamList"></DocContent>
|
||||
</template>
|
||||
<a-spin v-else tip="文档数据加载中...">
|
||||
<div style="padding: 20px 0;height: 100px;"></div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {toRefs, ref, reactive, onMounted, watch} from 'vue';
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import {useStore} from 'vuex';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
|
||||
import swaggerAnalysis from '../../../assets/core/SwaggerAnalysis.js'
|
||||
import DocContent from '../docView/DocContent.vue'
|
||||
import {markdownIt} from 'mavon-editor'
|
||||
import 'mavon-editor/dist/markdown/github-markdown.min.css'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
|
||||
export default {
|
||||
components: { DocContent },
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
let activePage = ref('doc');
|
||||
let requestParamList = ref([]);
|
||||
let responseParamList = ref([]);
|
||||
let docInfoShow = ref({
|
||||
url: '',
|
||||
description: '',
|
||||
method: '',
|
||||
consumes: '',
|
||||
produces: '',
|
||||
});
|
||||
let isLoadSuccess = ref(false);
|
||||
let intervalNum = 0;
|
||||
let intervalTimer = undefined;
|
||||
const initLoadDocument = () => {
|
||||
let path = route.query.path + '.' + route.query.method;
|
||||
if (Object.keys(store.state.swaggerUrlMethodMap).length <= 0) {
|
||||
console.log('文档尚未加载,等待加载完成');
|
||||
if (!intervalTimer) {
|
||||
intervalTimer = setInterval(() => {
|
||||
if (isLoadSuccess.value || intervalNum++ > 50) {
|
||||
clearInterval(intervalTimer);
|
||||
return;
|
||||
}
|
||||
if (Object.keys(store.state.swaggerUrlMethodMap).length > 0) {
|
||||
console.log('文档内容改变,重新加载文档');
|
||||
initLoadDocument();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let docInfo = store.state.swaggerUrlMethodMap[path];
|
||||
if (!docInfo) {
|
||||
message.error('没有找到对应的文档');
|
||||
return;
|
||||
}
|
||||
isLoadSuccess.value = true;
|
||||
store.commit('addTableName', {key: route.fullPath, val: docInfo.summary});
|
||||
// 解析接口说明
|
||||
let consumes = '', produces = '';
|
||||
if (docInfo.consumes && docInfo.consumes.length > 0) {
|
||||
consumes = docInfo.consumes.join(' ');
|
||||
}
|
||||
if (docInfo.produces && docInfo.produces.length > 0) {
|
||||
produces = docInfo.produces.join(' ');
|
||||
}
|
||||
let description = markdownIt.render(docInfo.description || docInfo.summary || '');
|
||||
docInfoShow.value = {
|
||||
url: docInfo.url,
|
||||
description: description,
|
||||
method: docInfo.method || '',
|
||||
consumes: consumes,
|
||||
produces: produces,
|
||||
};
|
||||
// 解析请求参数
|
||||
let definitionsDataMap = store.state.swaggerDefinitions;
|
||||
requestParamList.value = swaggerAnalysis.getRequestParamList(docInfo.parameters, definitionsDataMap);
|
||||
responseParamList.value = swaggerAnalysis.getResponseParamList(docInfo.responses, definitionsDataMap);
|
||||
}
|
||||
onMounted(() => {
|
||||
initLoadDocument();
|
||||
});
|
||||
const changePage = () => {
|
||||
}
|
||||
return {
|
||||
docInfoShow,
|
||||
activePage,
|
||||
changePage,
|
||||
isLoadSuccess,
|
||||
requestParamList,
|
||||
responseParamList,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user