刘力
3 years ago
21 changed files with 875 additions and 41 deletions
-
1src/assets/styles/index.scss
-
89src/assets/styles/topbar.scss
-
81src/components/Breadcrumb/index.vue
-
44src/components/Hamburger/index.vue
-
119src/layout/components/Navbar.vue
-
24src/layout/components/Sidebar/FixiOSBug.js
-
0src/layout/components/Sidebar/Item.vue
-
37src/layout/components/Sidebar/Link.vue
-
82src/layout/components/Sidebar/Logo.vue
-
79src/layout/components/Sidebar/SidebarItem.vue
-
49src/layout/components/Sidebar/index.vue
-
31src/layout/components/TopMenus.vue
-
207src/layout/components/Topbar.vue
-
4src/layout/components/index.js
-
39src/layout/index.vue
-
1src/router/index.js
-
11src/router/routers.js
-
6src/settings.js
-
3src/store/getters.js
-
9src/store/modules/permission.js
-
0src/views/home.vue
@ -0,0 +1,89 @@ |
|||||
|
.top-nav { |
||||
|
// margin-left: $sideBarWidth; |
||||
|
width: 100%; |
||||
|
background-color: #304156; |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
z-index: 1001; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
.log { |
||||
|
padding: 0 20px; |
||||
|
line-height: 56px; |
||||
|
font-size: 24px; |
||||
|
font-weight: bold; |
||||
|
color: rgb(191, 203, 217); |
||||
|
float: left; |
||||
|
} |
||||
|
.el-menu { |
||||
|
float: left; |
||||
|
border: none!important; |
||||
|
background-color: #304156; |
||||
|
|
||||
|
.nav-item { |
||||
|
display: inline-block; |
||||
|
.el-menu-item { |
||||
|
color: rgb(191, 203, 217); |
||||
|
&:hover { |
||||
|
background-color: $subMenuHover !important; |
||||
|
} |
||||
|
&:focus { |
||||
|
background-color: $subMenuHover !important; |
||||
|
// color: $subMenuActiveText !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.right-menu { |
||||
|
float: right; |
||||
|
height: 100%; |
||||
|
|
||||
|
&:focus { |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.right-menu-item { |
||||
|
display: inline-block; |
||||
|
padding: 0 8px; |
||||
|
height: 100%; |
||||
|
font-size: 18px; |
||||
|
color: #5a5e66; |
||||
|
vertical-align: text-bottom; |
||||
|
|
||||
|
&.hover-effect { |
||||
|
cursor: pointer; |
||||
|
transition: background .3s; |
||||
|
|
||||
|
&:hover { |
||||
|
background: rgba(0, 0, 0, .025) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.avatar-container { |
||||
|
margin-right: 30px; |
||||
|
|
||||
|
.avatar-wrapper { |
||||
|
margin-top: 5px; |
||||
|
position: relative; |
||||
|
|
||||
|
.user-avatar { |
||||
|
cursor: pointer; |
||||
|
width: 40px; |
||||
|
height: 40px; |
||||
|
border-radius: 10px; |
||||
|
} |
||||
|
|
||||
|
.el-icon-caret-bottom { |
||||
|
cursor: pointer; |
||||
|
position: absolute; |
||||
|
right: -20px; |
||||
|
top: 25px; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,81 @@ |
|||||
|
<template> |
||||
|
<el-breadcrumb class="app-breadcrumb" separator="/"> |
||||
|
<transition-group name="breadcrumb"> |
||||
|
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path"> |
||||
|
<span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span> |
||||
|
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a> |
||||
|
</el-breadcrumb-item> |
||||
|
</transition-group> |
||||
|
</el-breadcrumb> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import pathToRegexp from 'path-to-regexp' |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
levelList: null |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
$route(route) { |
||||
|
// if you go to the redirect page, do not update the breadcrumbs |
||||
|
if (route.path.startsWith('/redirect/')) { |
||||
|
return |
||||
|
} |
||||
|
this.getBreadcrumb() |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
this.getBreadcrumb() |
||||
|
}, |
||||
|
methods: { |
||||
|
getBreadcrumb() { |
||||
|
// only show routes with meta.title |
||||
|
let matched = this.$route.matched.filter(item => item.meta && item.meta.title) |
||||
|
const first = matched[0] |
||||
|
|
||||
|
if (!this.isDashboard(first)) { |
||||
|
matched = [{ path: '/dashboard', meta: { title: '首页' }}].concat(matched) |
||||
|
} |
||||
|
|
||||
|
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false) |
||||
|
}, |
||||
|
isDashboard(route) { |
||||
|
const name = route && route.name |
||||
|
if (!name) { |
||||
|
return false |
||||
|
} |
||||
|
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase() |
||||
|
}, |
||||
|
pathCompile(path) { |
||||
|
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561 |
||||
|
const { params } = this.$route |
||||
|
var toPath = pathToRegexp.compile(path) |
||||
|
return toPath(params) |
||||
|
}, |
||||
|
handleLink(item) { |
||||
|
const { redirect, path } = item |
||||
|
if (redirect) { |
||||
|
this.$router.push(redirect) |
||||
|
return |
||||
|
} |
||||
|
this.$router.push(this.pathCompile(path)) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.app-breadcrumb.el-breadcrumb { |
||||
|
display: inline-block; |
||||
|
font-size: 14px; |
||||
|
line-height: 50px; |
||||
|
margin-left: 8px; |
||||
|
.no-redirect { |
||||
|
color: #97a8be; |
||||
|
cursor: text; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,44 @@ |
|||||
|
<template> |
||||
|
<div style="padding: 0 15px;" @click="toggleClick"> |
||||
|
<svg |
||||
|
:class="{'is-active':isActive}" |
||||
|
class="hamburger" |
||||
|
viewBox="0 0 1024 1024" |
||||
|
xmlns="http://www.w3.org/2000/svg" |
||||
|
width="64" |
||||
|
height="64" |
||||
|
> |
||||
|
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" /> |
||||
|
</svg> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'Hamburger', |
||||
|
props: { |
||||
|
isActive: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
toggleClick() { |
||||
|
this.$emit('toggleClick') |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.hamburger { |
||||
|
display: inline-block; |
||||
|
vertical-align: middle; |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
} |
||||
|
|
||||
|
.hamburger.is-active { |
||||
|
transform: rotate(180deg); |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,119 @@ |
|||||
|
<template> |
||||
|
<div class="navbar"> |
||||
|
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> |
||||
|
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { mapGetters } from 'vuex' |
||||
|
import Breadcrumb from '@/components/Breadcrumb' |
||||
|
import Hamburger from '@/components/Hamburger' |
||||
|
// import Avatar from '@/assets/images/avatar.png' |
||||
|
// import variables from '@/assets/styles/variables.scss' |
||||
|
// import Sidebar from './Sidebar/index' |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
Breadcrumb, |
||||
|
Hamburger |
||||
|
}, |
||||
|
computed: { |
||||
|
...mapGetters(['sidebar']) |
||||
|
}, |
||||
|
methods: { |
||||
|
toggleSideBar() { |
||||
|
this.$store.dispatch('app/toggleSideBar') |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.navbar { |
||||
|
height: 60px; |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
background: #fff; |
||||
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); |
||||
|
|
||||
|
.navmenu { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.hamburger-container { |
||||
|
line-height: 46px; |
||||
|
height: 100%; |
||||
|
float: left; |
||||
|
cursor: pointer; |
||||
|
transition: background 0.3s; |
||||
|
-webkit-tap-highlight-color: transparent; |
||||
|
|
||||
|
&:hover { |
||||
|
background: rgba(0, 0, 0, 0.025); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.breadcrumb-container { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.errLog-container { |
||||
|
display: inline-block; |
||||
|
vertical-align: top; |
||||
|
} |
||||
|
|
||||
|
.right-menu { |
||||
|
float: right; |
||||
|
height: 100%; |
||||
|
line-height: 50px; |
||||
|
|
||||
|
&:focus { |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.right-menu-item { |
||||
|
display: inline-block; |
||||
|
padding: 0 8px; |
||||
|
height: 100%; |
||||
|
font-size: 18px; |
||||
|
color: #5a5e66; |
||||
|
vertical-align: text-bottom; |
||||
|
|
||||
|
&.hover-effect { |
||||
|
cursor: pointer; |
||||
|
transition: background 0.3s; |
||||
|
|
||||
|
&:hover { |
||||
|
background: rgba(0, 0, 0, 0.025); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.avatar-container { |
||||
|
margin-right: 30px; |
||||
|
|
||||
|
.avatar-wrapper { |
||||
|
margin-top: 5px; |
||||
|
position: relative; |
||||
|
|
||||
|
.user-avatar { |
||||
|
cursor: pointer; |
||||
|
width: 40px; |
||||
|
height: 40px; |
||||
|
border-radius: 10px; |
||||
|
} |
||||
|
|
||||
|
.el-icon-caret-bottom { |
||||
|
cursor: pointer; |
||||
|
position: absolute; |
||||
|
right: -20px; |
||||
|
top: 25px; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,24 @@ |
|||||
|
export default { |
||||
|
computed: { |
||||
|
device() { |
||||
|
return this.$store.state.app.device |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.fixBugIniOS() |
||||
|
}, |
||||
|
methods: { |
||||
|
fixBugIniOS() { |
||||
|
const $subMenu = this.$refs.subMenu |
||||
|
if ($subMenu) { |
||||
|
const handleMouseleave = $subMenu.handleMouseleave |
||||
|
$subMenu.handleMouseleave = (e) => { |
||||
|
if (this.device === 'mobile') { |
||||
|
return |
||||
|
} |
||||
|
handleMouseleave(e) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,37 @@ |
|||||
|
<template> |
||||
|
<!-- eslint-disable vue/require-component-is --> |
||||
|
<!-- 防止语法报错 --> |
||||
|
<component v-bind="linkProps(to)"> |
||||
|
<slot /> |
||||
|
</component> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { isExternal } from '@/utils/validate' |
||||
|
|
||||
|
export default { |
||||
|
props: { |
||||
|
to: { |
||||
|
type: String, |
||||
|
required: true |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
linkProps(url) { |
||||
|
if (isExternal(url)) { |
||||
|
return { |
||||
|
is: 'a', |
||||
|
href: url, |
||||
|
target: '_blank', |
||||
|
rel: 'noopener' |
||||
|
} |
||||
|
} |
||||
|
return { |
||||
|
is: 'router-link', |
||||
|
to: url |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
</script> |
@ -0,0 +1,82 @@ |
|||||
|
<template> |
||||
|
<div class="sidebar-logo-container" :class="{'collapse':collapse}"> |
||||
|
<transition name="sidebarLogoFade"> |
||||
|
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> |
||||
|
<img v-if="logo" :src="logo" class="sidebar-logo"> |
||||
|
<h1 v-else class="sidebar-title">{{ title }}</h1> |
||||
|
</router-link> |
||||
|
<router-link v-else key="expand" class="sidebar-logo-link" to="/"> |
||||
|
<img v-if="logo" :src="logo" class="sidebar-logo"> |
||||
|
<h1 class="sidebar-title">{{ title }} </h1> |
||||
|
</router-link> |
||||
|
</transition> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import Logo from '@/assets/images/logo.png' |
||||
|
export default { |
||||
|
name: 'SidebarLogo', |
||||
|
props: { |
||||
|
collapse: { |
||||
|
type: Boolean, |
||||
|
required: true |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
title: '阅行资源后台管理系统', |
||||
|
logo: Logo |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.sidebarLogoFade-enter-active { |
||||
|
transition: opacity 1.5s; |
||||
|
} |
||||
|
|
||||
|
.sidebarLogoFade-enter, |
||||
|
.sidebarLogoFade-leave-to { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
.sidebar-logo-container { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: 50px; |
||||
|
line-height: 50px; |
||||
|
text-align: center; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
& .sidebar-logo-link { |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
|
||||
|
& .sidebar-logo { |
||||
|
width: 32px; |
||||
|
height: 32px; |
||||
|
vertical-align: middle; |
||||
|
margin-right: 6px; |
||||
|
} |
||||
|
|
||||
|
& .sidebar-title { |
||||
|
display: inline-block; |
||||
|
margin: 0; |
||||
|
color: #fff; |
||||
|
font-weight: 600; |
||||
|
line-height: 50px; |
||||
|
font-size: 14px; |
||||
|
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.collapse { |
||||
|
.sidebar-logo { |
||||
|
margin-right: 0px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,79 @@ |
|||||
|
<template> |
||||
|
<div v-if="!item.hidden" class="menu-wrapper"> |
||||
|
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow"> |
||||
|
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)"> |
||||
|
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }"> |
||||
|
<item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" /> |
||||
|
</el-menu-item> |
||||
|
</app-link> |
||||
|
</template> |
||||
|
|
||||
|
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body> |
||||
|
<template slot="title"> |
||||
|
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> |
||||
|
</template> |
||||
|
<sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child" :base-path="resolvePath(child.path)" class="nest-menu" /> |
||||
|
</el-submenu> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import path from 'path' |
||||
|
import { isExternal } from '@/utils/validate' |
||||
|
|
||||
|
export default { |
||||
|
name: 'SidebarItem', |
||||
|
|
||||
|
props: { |
||||
|
// route object |
||||
|
item: { |
||||
|
type: Object, |
||||
|
required: true |
||||
|
}, |
||||
|
isNest: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
basePath: { |
||||
|
type: String, |
||||
|
default: '' |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
this.onlyOneChild = null |
||||
|
return {} |
||||
|
}, |
||||
|
methods: { |
||||
|
hasOneShowingChild(children = [], parent) { |
||||
|
const showingChildren = children.filter((item) => { |
||||
|
if (item.hidden) { |
||||
|
return false |
||||
|
} else { |
||||
|
this.onlyOneChild = item |
||||
|
return true |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
if (showingChildren.length === 1) { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
if (showingChildren.length === 0) { |
||||
|
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true } |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
return false |
||||
|
}, |
||||
|
resolvePath(routePath) { |
||||
|
if (isExternal(routePath)) { |
||||
|
return routePath |
||||
|
} |
||||
|
if (isExternal(this.basePath)) { |
||||
|
return this.basePath |
||||
|
} |
||||
|
return path.resolve(this.basePath, routePath) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
@ -0,0 +1,49 @@ |
|||||
|
<template> |
||||
|
<div :class="{ 'has-logo': showLogo }"> |
||||
|
<logo v-if="showLogo" :collapse="isCollapse" /> |
||||
|
<el-scrollbar wrap-class="scrollbar-wrapper"> |
||||
|
<el-menu :default-active="activeMenu" :collapse="isCollapse" :background-color="variables.menuBg" :text-color="variables.menuText" :active-text-color="variables.menuActiveText" :collapse-transition="false" mode="vertical"> |
||||
|
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" /> |
||||
|
</el-menu> |
||||
|
</el-scrollbar> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { mapGetters } from 'vuex' |
||||
|
import Logo from './Logo' |
||||
|
import SidebarItem from './SidebarItem' |
||||
|
import variables from '@/assets/styles/variables.scss' |
||||
|
|
||||
|
export default { |
||||
|
components: { SidebarItem, Logo }, |
||||
|
props: { |
||||
|
|
||||
|
}, |
||||
|
computed: { |
||||
|
...mapGetters(['sidebar']), |
||||
|
routes() { |
||||
|
return this.$store.state.permission.currentRoutes.children |
||||
|
}, |
||||
|
activeMenu() { |
||||
|
const route = this.$route |
||||
|
const { meta, path } = route |
||||
|
|
||||
|
if (meta.activeMenu) { |
||||
|
return meta.activeMenu |
||||
|
} |
||||
|
return path |
||||
|
}, |
||||
|
showLogo() { |
||||
|
return this.$store.state.settings.sidebarLogo |
||||
|
}, |
||||
|
variables() { |
||||
|
return variables |
||||
|
}, |
||||
|
isCollapse() { |
||||
|
return !this.sidebar.opened |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
</script> |
@ -1,31 +0,0 @@ |
|||||
<template> |
|
||||
<div> |
|
||||
<el-menu :default-active="activeMenu" :background-color="variables.menuBg" :text-color="variables.menuText" :active-text-color="variables.menuActiveText" :collapse-transition="false" mode="horizontal"> |
|
||||
<el-menu-item v-for="route in sidebarRouters" :key="route.path" :item="route" :base-path="route.path" /> |
|
||||
</el-menu> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
import { mapGetters } from 'vuex' |
|
||||
import variables from '@/assets/styles/variables.scss' |
|
||||
|
|
||||
export default { |
|
||||
name: 'TopMenus', |
|
||||
computed: { |
|
||||
...mapGetters(['sidebarRouters', 'sidebar']), |
|
||||
activeMenu() { |
|
||||
const route = this.$route |
|
||||
const { meta, path } = route |
|
||||
|
|
||||
if (meta.activeMenu) { |
|
||||
return meta.activeMenu |
|
||||
} |
|
||||
return path |
|
||||
}, |
|
||||
variables() { |
|
||||
return variables |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
@ -0,0 +1,207 @@ |
|||||
|
<template> |
||||
|
<div class="top-nav"> |
||||
|
<el-menu :active-text-color="variables.menuActiveText" :default-active="activeMenu" mode="horizontal" @select="handleSelect"> |
||||
|
<div v-for="item in routes" :key="item.path" class="nav-item"> |
||||
|
<app-link :to="resolvePath(item)"> |
||||
|
<el-menu-item v-if="!item.hidden" :index="item.path"> |
||||
|
{{ item.meta ? item.meta.title : item.children[0].meta.title }} |
||||
|
</el-menu-item> |
||||
|
</app-link> |
||||
|
</div> |
||||
|
</el-menu> |
||||
|
|
||||
|
<div class="right-menu"> |
||||
|
<!-- <template v-if="device !== 'mobile'"> |
||||
|
<search id="header-search" class="right-menu-item" /> |
||||
|
<el-tooltip content="项目文档" effect="dark" placement="bottom"> |
||||
|
<Doc class="right-menu-item hover-effect" /> |
||||
|
</el-tooltip> |
||||
|
<el-tooltip content="全屏缩放" effect="dark" placement="bottom"> |
||||
|
<screenfull id="screenfull" class="right-menu-item hover-effect" /> |
||||
|
</el-tooltip> |
||||
|
<el-tooltip content="布局设置" effect="dark" placement="bottom"> |
||||
|
<size-select id="size-select" class="right-menu-item hover-effect" /> |
||||
|
</el-tooltip> |
||||
|
</template> --> |
||||
|
|
||||
|
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click"> |
||||
|
<div class="avatar-wrapper"> |
||||
|
<img :src="user.avatarName ? baseApi + '/avatar/' + user.avatarName : Avatar" class="user-avatar"> |
||||
|
<i class="el-icon-caret-bottom" /> |
||||
|
</div> |
||||
|
<el-dropdown-menu slot="dropdown"> |
||||
|
<span style="display:block;" @click="show = true"> |
||||
|
<el-dropdown-item> |
||||
|
布局设置 |
||||
|
</el-dropdown-item> |
||||
|
</span> |
||||
|
<router-link to="/user/center"> |
||||
|
<el-dropdown-item> |
||||
|
个人中心 |
||||
|
</el-dropdown-item> |
||||
|
</router-link> |
||||
|
<span style="display:block;" @click="open"> |
||||
|
<el-dropdown-item divided> |
||||
|
退出登录 |
||||
|
</el-dropdown-item> |
||||
|
</span> |
||||
|
</el-dropdown-menu> |
||||
|
</el-dropdown> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { mapGetters } from 'vuex' |
||||
|
import AppLink from './Sidebar/Link' |
||||
|
import variables from '@/assets/styles/variables.scss' |
||||
|
import { isExternal } from '@/utils/validate' |
||||
|
import Avatar from '@/assets/images/avatar.png' |
||||
|
|
||||
|
export default { |
||||
|
name: 'Topbar', |
||||
|
components: { |
||||
|
AppLink |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
Avatar: Avatar, |
||||
|
dialogVisible: false, |
||||
|
routes: this.$store.getters.navMenus |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
...mapGetters(['device', 'user', 'baseApi']), |
||||
|
activeMenu() { |
||||
|
const route = this.$route |
||||
|
const { meta, path } = route |
||||
|
console.log(this.routes) |
||||
|
// if set path, the sidebar will highlight the path you set |
||||
|
if (meta.activeMenu) { |
||||
|
return meta.activeMenu |
||||
|
} |
||||
|
// 如果是首页,首页高亮 |
||||
|
if (path === '/dashboard') { |
||||
|
return '/' |
||||
|
} |
||||
|
// 如果不是首页,高亮一级菜单 |
||||
|
const activeMenu = '/' + path.split('/')[1] |
||||
|
return activeMenu |
||||
|
}, |
||||
|
variables() { |
||||
|
return variables |
||||
|
}, |
||||
|
sidebar() { |
||||
|
return this.$store.state.app.sidebar |
||||
|
}, |
||||
|
show: { |
||||
|
get() { |
||||
|
return this.$store.state.settings.showSettings |
||||
|
}, |
||||
|
set(val) { |
||||
|
this.$store.dispatch('settings/changeSetting', { |
||||
|
key: 'showSettings', |
||||
|
value: val |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
methods: { |
||||
|
// 通过当前路径找到二级菜单对应项,存到store,用来渲染左侧菜单 |
||||
|
initCurrentRoutes() { |
||||
|
const { path } = this.$route |
||||
|
console.log(this.routes) |
||||
|
let route = this.routes.find( |
||||
|
item => item.path === '/' + path.split('/')[1] |
||||
|
) |
||||
|
// 如果找不到这个路由,说明是首页 |
||||
|
if (!route) { |
||||
|
route = this.routes.find(item => item.path === '/') |
||||
|
} |
||||
|
this.$store.commit('permission/SET_CURRENT_ROUTES', route) |
||||
|
this.setSidebarHide(route) |
||||
|
}, |
||||
|
// 判断该路由是否只有一个子项或者没有子项,如果是,则在一级菜单添加跳转路由 |
||||
|
isOnlyOneChild(item) { |
||||
|
if (item.children && item.children.length === 1) { |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
}, |
||||
|
resolvePath(item) { |
||||
|
// 如果是个完成的url直接返回 |
||||
|
if (isExternal(item.path)) { |
||||
|
return item.path |
||||
|
} |
||||
|
// 如果是首页,就返回重定向路由 |
||||
|
if (item.path === '/') { |
||||
|
const path = item.redirect |
||||
|
return path |
||||
|
} |
||||
|
|
||||
|
// 如果有子项,默认跳转第一个子项路由 |
||||
|
let path = '' |
||||
|
/** |
||||
|
* item 路由子项 |
||||
|
* parent 路由父项 |
||||
|
*/ |
||||
|
const getDefaultPath = (item, parent) => { |
||||
|
// 如果path是个外部链接(不建议),直接返回链接,存在个问题:如果是外部链接点击跳转后当前页内容还是上一个路由内容 |
||||
|
if (isExternal(item.path)) { |
||||
|
path = item.path |
||||
|
return |
||||
|
} |
||||
|
// 第一次需要父项路由拼接,所以只是第一个传parent |
||||
|
if (parent) { |
||||
|
path += (parent.path + '/' + item.path) |
||||
|
} else { |
||||
|
path += ('/' + item.path) |
||||
|
} |
||||
|
// 如果还有子项,继续递归 |
||||
|
if (item.children) { |
||||
|
getDefaultPath(item.children[0]) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (item.children) { |
||||
|
getDefaultPath(item.children[0], item) |
||||
|
return path |
||||
|
} |
||||
|
|
||||
|
return item.path |
||||
|
}, |
||||
|
handleSelect(key, keyPath) { |
||||
|
// 把选中路由的子路由保存store |
||||
|
const route = this.routes.find(item => item.path === key) |
||||
|
this.$store.commit('permission/SET_CURRENT_ROUTES', route) |
||||
|
this.setSidebarHide(route) |
||||
|
}, |
||||
|
// 设置侧边栏的显示和隐藏 |
||||
|
setSidebarHide(route) { |
||||
|
if (!route.children || route.children.length === 1) { |
||||
|
this.$store.dispatch('app/toggleSideBarHide', true) |
||||
|
} else { |
||||
|
this.$store.dispatch('app/toggleSideBarHide', false) |
||||
|
} |
||||
|
}, |
||||
|
toggleSideBar() { |
||||
|
this.$store.dispatch('app/toggleSideBar') |
||||
|
}, |
||||
|
open() { |
||||
|
this.$confirm('确定注销并退出系统吗?', '提示', { |
||||
|
confirmButtonText: '确定', |
||||
|
cancelButtonText: '取消', |
||||
|
type: 'warning' |
||||
|
}).then(() => { |
||||
|
this.logout() |
||||
|
}) |
||||
|
}, |
||||
|
logout() { |
||||
|
this.$store.dispatch('LogOut').then(() => { |
||||
|
location.reload() |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
@ -1,2 +1,4 @@ |
|||||
export { default as AppMain } from './AppMain' |
export { default as AppMain } from './AppMain' |
||||
export { default as TopMenus } from './TopMenus' |
|
||||
|
export { default as Navbar } from './Navbar' |
||||
|
export { default as Sidebar } from './Sidebar' |
||||
|
export { default as Topbar } from './Topbar' |
Write
Preview
Loading…
Cancel
Save
Reference in new issue