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