使用vitejs+vue3+antdv重构swagger文档展示

This commit is contained in:
暮光:城中城
2021-10-17 19:50:22 +08:00
parent 28bcbfd379
commit 9267aed477
72 changed files with 4236 additions and 3 deletions

View File

@@ -0,0 +1,15 @@
<template>
<router-view></router-view>
</template>
<script>
export default {
name: 'EmptyLayout',
components: {},
props: [],
data() {
return {}
},
methods: {}
}
</script>

View File

@@ -0,0 +1,42 @@
<template>
<div class="footer">
<div class="links">
<a target="_blank" :key="index" :href="item.link ? item.link : 'javascript: void(0)'" v-for="(item, index) in linkList">
<a-icon v-if="item.icon" :type="item.icon"/>{{item.name}}
</a>
</div>
<div class="copyright">
Copyright<a-icon type="copyright" /> {{copyright}}
</div>
</div>
</template>
<script>
export default {
name: 'GlobalFooter',
props: ['copyright', 'linkList']
}
</script>
<style scoped>
.footer {
padding: 0 16px;
margin: 48px 0 24px;
text-align: center;
}
.footer .copyright {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
.footer .links {
margin-bottom: 8px;
}
.footer .links a:not(:last-child) {
margin-right: 40px;
}
.footer .links a {
color: rgba(0, 0, 0, 0.45);
-webkit-transition: all .3s;
transition: all .3s;
}
</style>

View File

@@ -0,0 +1,164 @@
<template>
<a-layout class="swagger-menu-trigger">
<a-layout-sider theme="light" :trigger="null" collapsible v-model:collapsed="appMenuCollapsed" :width="rightAsideWidth" style="height: 100vh;overflow: auto;">
<div class="logo">
<router-link to="/doc/console">
<img src="../../assets/logo.png">
<h1>swagger文档管理</h1>
</router-link>
</div>
<menu-layout></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="60px">
<MenuUnfoldOutlined class="trigger" v-if="appMenuCollapsed" @click="appMenuCollapsed = !appMenuCollapsed"/>
<MenuFoldOutlined class="trigger" v-else @click="appMenuCollapsed = !appMenuCollapsed"/>
</a-col>
<a-col flex="auto" style="text-align: center;">
<span v-if="initialEnv === 'newGray'" class="initial-env">当前环境灰度</span>
</a-col>
<a-col flex="400px" style="text-align: right;padding-right: 20px;">
<header-avatar></header-avatar>
</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 HeaderAvatar from './HeaderAvatar.vue'
import MenuLayout from './MenuLayout.vue'
import GlobalFooter from './GlobalFooter.vue'
import {BarChartOutlined, MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons-vue';
const minHeight = window.innerHeight - 64 - 122;
export default {
name: 'GlobalLayout',
components: {HeaderAvatar, MenuLayout, GlobalFooter, BarChartOutlined, MenuFoldOutlined, MenuUnfoldOutlined},
data() {
return {
minHeight: minHeight + 'px',
appMenuCollapsed: false,
rightAsideWidth: 250
}
},
computed: {
initialEnv () {
return this.$store.state.initialEnv;
}
},
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 > 250)) {
startX = endX;
this.rightAsideWidth -= moveLen;
}
};
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;
}
.swagger-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>

View File

@@ -0,0 +1,67 @@
<template>
<a-dropdown trigger="click">
<a class="ant-dropdown-link" @click.prevent style="display: inline-block; height: 100%; vertical-align: initial;">
<UserOutlined /> {{currUser.userName || '-'}}
</a>
<template #overlay>
<a-menu >
<a-menu-item @click="showConsole" key="1">控制台</a-menu-item>
<a-menu-divider />
<a-menu-item @click="showAbout" key="2">关于</a-menu-item>
<a-menu-item @click="showMyInfo" key="3">我的资料</a-menu-item>
<a-menu-item @click="userSignOut" key="4">退出登录</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<about-dialog ref="aboutDialog"></about-dialog>
</template>
<script>
import {zyplayerApi} from '../../api/index';
import {getZyplayerApiBaseUrl} from '../../api/request/utils.js';
import aboutDialog from '../../views/common/AboutDialog.vue'
import {DownOutlined, UserOutlined} from '@ant-design/icons-vue';
export default {
name: 'HeaderAvatar',
data() {
return {
currUser: {},
};
},
components: {DownOutlined, UserOutlined, aboutDialog},
mounted() {
this.getSelfUserInfo();
},
methods: {
showAbout() {
this.$refs.aboutDialog.show();
},
showConsole() {
window.open(getZyplayerApiBaseUrl(), '_blank');
},
showMyInfo() {
this.$router.push({path: '/user/myInfo'});
},
userSignOut() {
zyplayerApi.userLogout().then(() => {
location.reload();
});
},
getSelfUserInfo() {
zyplayerApi.getSelfUserInfo().then(json=>{
this.currUser = json.data;
});
},
},
}
</script>
<style scoped>
.avatar {
margin: 20px 4px 20px 0;
color: #1890ff;
background: hsla(0, 0%, 100%, .85);
vertical-align: middle;
}
</style>

View File

@@ -0,0 +1,54 @@
<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'"/>
</template>
<span>{{menuItem.name}}</span>
</router-link>
</a-menu-item>
</template>
</template>
<script>
import {
StarOutlined,
SettingOutlined,
CarryOutOutlined,
FileTextOutlined,
DashboardOutlined
} from '@ant-design/icons-vue';
export default {
name: 'MenuLayoutChildren',
props: {
menuItem: Object,
},
data() {
return {}
},
components: {
StarOutlined, SettingOutlined, CarryOutOutlined, FileTextOutlined,
DashboardOutlined
},
methods: {
haveShowChildren(children) {
return children.filter(item => (!item.meta || !item.meta.hidden)).length > 0;
},
}
}
</script>

View File

@@ -0,0 +1,109 @@
<template>
<div class="menu-layout">
<a-menu theme="light" mode="inline" :inline-collapsed="collapsed" v-model:openKeys="openKeys" v-model:selectedKeys="selectedKeys">
<menu-children-layout :menuItem="menuItem" v-for="menuItem in menuData"></menu-children-layout>
</a-menu>
<a-directory-tree :tree-data="treeData" v-model:expandedKeys="expandedKeys" @select="docChecked">
<a-tree-node key="0-0" title="parent 0">
<a-tree-node key="0-0-0" is-leaf >
<template #title>
<router-link :to="{path: '/doc/view', query: {id: 1}}">leaf 0-0</router-link>
</template>
</a-tree-node>
<a-tree-node key="0-0-1" is-leaf >
<template #title>
<router-link :to="{path: '/doc/view', query: {id: 2}}">leaf 0-1</router-link>
</template>
</a-tree-node>
</a-tree-node>
<a-tree-node key="0-1" title="parent 1">
<a-tree-node key="0-1-0" title="leaf 1-0" is-leaf />
<a-tree-node key="0-1-1" title="leaf 1-1" is-leaf />
</a-tree-node>
</a-directory-tree>
</div>
</template>
<script>
import MenuChildrenLayout from './MenuChildrenLayout.vue'
export default {
name: 'MenuLayout',
data() {
return {
menuData: [],
selectedKeys: [],
openKeys: [],
collapsed: false,
// 文档树
treeData: [
{
title: '用户管理接口文档',
key: '0-0',
children: [
{
title: '用户信息管理',
key: '0-0-0',
children: [
{title: '/getUserInfo', key: '0-0-0-0', isLeaf: true, path: '/doc/view', query: {path: '/getUserInfo'}},
{title: '/deleteUserInfo', key: '0-0-0-1', isLeaf: true, path: '/doc/view', query: {path: '/deleteUserInfo'}},
{title: '/updateUserInfo', key: '0-0-0-2', isLeaf: true, path: '/doc/view', query: {path: '/updateUserInfo'}},
],
},
],
},
],
expandedKeys: [],
}
},
watch:{
'$store.state.userInfo'(userInfo) {
}
},
components: {MenuChildrenLayout},
mounted() {
this.getMenuData();
let meta = this.$route.meta || {};
let path = this.$route.path;
if (!!meta.parentPath) {
path = meta.parentPath;
}
this.selectedKeys = [path];
let matched = this.$route.matched;
if (matched.length >= 1) {
this.openKeys = [matched[1].path];
}
},
methods: {
getMenuData() {
let menuData = this.$router.options.routes.find((item) => item.path === '/').children[0].children;
this.menuData = JSON.parse(JSON.stringify(menuData));
// 模拟数据返回,暂时不以这种展示
// setTimeout(() => {
// this.menuData.push({
// name: '用户管理接口',
// meta: {icon: 'FileTextOutlined'},
// children: [
// {
// path: '/doc/view?id=2',
// name: '获取用户信息',
// query: {id: 222}
// }, {
// path: '/doc/view?id=3',
// name: '删除用户',
// query: {id: 333}
// }
// ]
// });
// }, 1000);
},
docChecked(val, node) {
if (node.node.isLeaf) {
let dataRef = node.node.dataRef;
this.$router.push({path: dataRef.path, query: dataRef.query});
}
}
}
}
</script>

View File

@@ -0,0 +1,89 @@
<template>
<div class="page-layout">
<a-tabs type="card" v-model:activeKey="activePage" closable @tab-click="changePage" @edit="removePageTab" style="padding: 5px 10px 0;">
<a-tab-pane :tab="pageTabNameMap[item.fullPath]||item.name" :name="getRouteRealPath(item)" :fullPath="item.fullPath" :key="item.fullPath" v-for="item in pageList"/>
</a-tabs>
<keep-alive>
<router-view :key="$route.fullPath"/>
</keep-alive>
</div>
</template>
<script>
export default {
name: 'PageTableView',
components: {},
data() {
return {
pageList: [],
linkList: [],
activePage: '',
multiPage: true,
ignoreParamPath: [
"/data/export",
],
}
},
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);
},
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;}
.ant-tabs-bar{margin-bottom: 0;}
</style>