Ver código fonte

refactor:router&&sidemenu

Pan 7 anos atrás
pai
commit
86096e4eab

+ 17 - 17
src/components/Breadcrumb/index.vue

@@ -1,11 +1,11 @@
 <template>
   <el-breadcrumb class="app-breadcrumb" separator="/">
-  <transition-group name="breadcrumb">
-    <el-breadcrumb-item v-for="(item,index)  in levelList" :key="item.path">
-      <router-link v-if='item.redirect==="noredirect"||index==levelList.length-1' to="" class="no-redirect">{{item.name}}</router-link>
-      <router-link v-else :to="item.redirect||item.path">{{item.name}}</router-link>
-    </el-breadcrumb-item>
-     </transition-group>
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item,index)  in levelList" :key="item.path" v-if='item.meta.title'>
+        <span v-if='item.redirect==="noredirect"||index==levelList.length-1' class="no-redirect">{{item.meta.title}}</span>
+        <router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
+      </el-breadcrumb-item>
+    </transition-group>
   </el-breadcrumb>
 </template>
 
@@ -23,8 +23,8 @@ export default {
     getBreadcrumb() {
       let matched = this.$route.matched.filter(item => item.name)
       const first = matched[0]
-      if (first && (first.name !== 'Home' || first.path !== '')) {
-        matched = [{ name: 'Home', path: '/' }].concat(matched)
+      if (first && first.name !== 'dashboard') {
+        matched = [{ path: '/dashboard', meta: { title: 'dashboard' }}].concat(matched)
       }
       this.levelList = matched
     }
@@ -38,14 +38,14 @@ export default {
 </script>
 
 <style rel="stylesheet/scss" lang="scss" scoped>
-.app-breadcrumb.el-breadcrumb {
-  display: inline-block;
-  font-size: 14px;
-  line-height: 50px;
-  margin-left: 10px;
-  .no-redirect {
-    color: #97a8be;
-    cursor: text;
+  .app-breadcrumb.el-breadcrumb {
+    display: inline-block;
+    font-size: 14px;
+    line-height: 50px;
+    margin-left: 10px;
+    .no-redirect {
+      color: #97a8be;
+      cursor: text;
+    }
   }
-}
 </style>

+ 56 - 0
src/components/ScrollBar/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <div class='scroll-container' ref='scrollContainer' @mousewheel="handleScroll">
+    <div class='scroll-wrapper' ref='scrollWrapper' :style="{top: top + 'px'}">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+const delta = 15
+export default {
+  name: 'scrollBar',
+  data() {
+    return {
+      top: 0
+    }
+  },
+  methods: {
+    handleScroll(e) {
+      e.preventDefault()
+      const $container = this.$refs.scrollContainer
+      const $containerHeight = $container.offsetHeight
+      const $wrapper = this.$refs.scrollWrapper
+      const $wrapperHeight = $wrapper.offsetHeight
+      if (e.wheelDelta > 0) {
+        this.top = Math.min(0, this.top + e.wheelDelta)
+      } else {
+        if ($containerHeight - delta < $wrapperHeight) {
+          if (this.top < -($wrapperHeight - $containerHeight + delta)) {
+            this.top = this.top
+          } else {
+            this.top = Math.max(this.top + e.wheelDelta, $containerHeight - $wrapperHeight - delta)
+          }
+        } else {
+          this.top = 0
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+@import '../../styles/variables.scss';
+
+.scroll-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-color: $menuBg;
+  .scroll-wrapper {
+    position: absolute;
+     width: 100%!important;
+  }
+}
+</style>

+ 31 - 15
src/router/index.js

@@ -1,19 +1,23 @@
 import Vue from 'vue'
 import Router from 'vue-router'
 const _import = require('./_import_' + process.env.NODE_ENV)
-// in development env not use Lazy Loading,because Lazy Loading too many pages will cause webpack hot update too slow.so only in production use Lazy Loading
-
-/* layout */
-import Layout from '../views/layout/Layout'
+// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
+// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
 
 Vue.use(Router)
 
+/* Layout */
+import Layout from '../views/layout/Layout'
+
 /**
-* icon : the icon show in the sidebar
-* hidden : if `hidden:true` will not show in the sidebar
-* redirect : if `redirect:noredirect` will not redirct in the levelbar
-* noDropdown : if `noDropdown:true` will not has submenu in the sidebar
-* meta : `{ role: ['admin'] }`  will control the page role
+* hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
+* redirect: noredirect           if `redirect:noredirect` will no redirct in the breadcrumb
+* name:'router-name'             the name is used by <keep-alive> (must set!!!)
+* meta : {
+    role: ['admin','editor']     will control the page role (you can set multiple roles)
+    title: 'title'               the name show in submenu and breadcrumb (recommend set)
+    icon: 'svg-name'             the icon show in the sidebar,
+  }
 **/
 export const constantRouterMap = [
   { path: '/login', component: _import('login/index'), hidden: true },
@@ -24,7 +28,11 @@ export const constantRouterMap = [
     redirect: '/dashboard',
     name: 'Dashboard',
     hidden: true,
-    children: [{ path: 'dashboard', component: _import('dashboard/index') }]
+    children: [{
+      path: 'dashboard',
+      component: _import('dashboard/index'),
+      meta: { title: 'dashboard', icon: 'dashboard' }
+    }]
   },
 
   {
@@ -32,9 +40,14 @@ export const constantRouterMap = [
     component: Layout,
     redirect: 'noredirect',
     name: 'Example',
-    icon: 'example',
+    meta: { title: 'Example', icon: 'example' },
     children: [
-      { path: 'index', name: 'Form', icon: 'form', component: _import('page/form') }
+      {
+        path: 'index',
+        name: 'Form',
+        component: _import('page/form'),
+        meta: { title: 'Form', icon: 'form' }
+      }
     ]
   },
 
@@ -42,9 +55,12 @@ export const constantRouterMap = [
     path: '/table',
     component: Layout,
     redirect: '/table/index',
-    icon: 'table',
-    noDropdown: true,
-    children: [{ path: 'index', name: 'Table', component: _import('table/index'), meta: { role: ['admin'] }}]
+    children: [{
+      path: 'index',
+      name: 'Table',
+      component: _import('table/index'),
+      meta: { title: 'Table', icon: 'table', role: ['admin'] }}
+    ]
   },
 
   { path: '*', redirect: '/404', hidden: true }

+ 3 - 1
src/styles/index.scss

@@ -1,6 +1,8 @@
-@import './element-ui.scss';
+@import './variables.scss';
 @import './mixin.scss';
 @import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
 
 body {
   -moz-osx-font-smoothing: grayscale;

+ 100 - 0
src/styles/sidebar.scss

@@ -0,0 +1,100 @@
+#app {
+  // 主体区域
+  .main-container {
+    min-height: 100%;
+    transition: margin-left 0.28s;
+    margin-left: 180px;
+  } // 侧边栏
+  .sidebar-container {
+    transition: width 0.28s;
+    width: 180px!important;
+    height: 100%;
+    position: fixed;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1001;
+    a {
+      display: inline-block;
+      width: 100%;
+    }
+    .svg-icon {
+      margin-right: 16px;
+    }
+    .el-menu {
+      border: none;
+      width: 100%;
+    }
+  }
+  .hideSidebar {
+    .sidebar-container,.sidebar-container .el-menu {
+      width: 36px!important;
+      // overflow: inherit;
+    }
+    .main-container {
+      margin-left: 36px;
+    }
+  }
+  .hideSidebar {
+    .submenu-title-noDropdown {
+      padding-left: 10px!important;
+      position: relative;
+      span {
+        height: 0;
+        width: 0;
+        overflow: hidden;
+        visibility: hidden;
+        transition: opacity .3s cubic-bezier(.55, 0, .1, 1);
+        opacity: 0;
+        display: inline-block;
+      }
+      &:hover {
+        span {
+          display: block;
+          border-radius: 3px;
+          z-index: 1002;
+          width: 140px;
+          height: 56px;
+          visibility: visible;
+          position: absolute;
+          right: -145px;
+          text-align: left;
+          text-indent: 20px;
+          top: 0px;
+          background-color: $subMenuBg!important;
+          opacity: 1;
+        }
+      }
+    }
+    .el-submenu {
+      &>.el-submenu__title {
+        padding-left: 10px!important;
+        &>span {
+          display: none;
+        }
+        .el-submenu__icon-arrow {
+          display: none;
+        }
+      }
+      .nest-menu {
+        .el-submenu__icon-arrow {
+          display: block!important;
+        }
+        span {
+          display: inline-block!important;
+        }
+      }
+    }
+  }
+  .nest-menu .el-submenu>.el-submenu__title,
+  .el-submenu .el-menu-item {
+    min-width: 180px!important;
+    background-color: $subMenuBg!important;
+    &:hover {
+      background-color: $menuHover!important;
+    }
+  }
+  .el-menu--collapse .el-menu .el-submenu{
+    min-width: 180px!important;
+  }
+}

+ 4 - 0
src/styles/variables.scss

@@ -0,0 +1,4 @@
+//sidebar
+$menuBg:#304156;
+$subMenuBg:#1f2d3d;
+$menuHover:#001528;

+ 8 - 52
src/views/layout/Layout.vue

@@ -1,8 +1,6 @@
 <template>
   <div class="app-wrapper" :class="{hideSidebar:!sidebar.opened}">
-    <div class="sidebar-wrapper">
-      <sidebar class="sidebar-container"></sidebar>
-    </div>
+    <sidebar class="sidebar-container"></sidebar>
     <div class="main-container">
       <navbar></navbar>
       <app-main></app-main>
@@ -10,7 +8,6 @@
   </div>
 </template>
 
-
 <script>
 import { Navbar, Sidebar, AppMain } from '@/views/layout/components'
 
@@ -30,52 +27,11 @@ export default {
 </script>
 
 <style rel="stylesheet/scss" lang="scss" scoped>
-    @import "src/styles/mixin.scss";
-    .app-wrapper {
-        @include clearfix;
-        position: relative;
-        height: 100%;
-        width: 100%;
-        &.hideSidebar {
-            .sidebar-wrapper {
-                transform: translate(-140px, 0);
-                .sidebar-container {
-                    transform: translate(132px, 0);
-                }
-                &:hover {
-                    transform: translate(0, 0);
-                    .sidebar-container {
-                        transform: translate(0, 0);
-                    }
-                }
-            }
-            .main-container {
-                margin-left: 40px;
-            }
-        }
-        .sidebar-wrapper {
-            width: 180px;
-            position: fixed;
-            top: 0;
-            bottom: 0;
-            left: 0;
-            z-index: 1001;
-            overflow: hidden;
-            transition: all .28s ease-out;
-        }
-        .sidebar-container {
-            transition: all .28s ease-out;
-            position: absolute;
-            top: 0;
-            bottom: 0;
-            left: 0;
-            right: -17px;
-            overflow-y: scroll;
-        }
-        .main-container {
-            min-height: 100%;
-            transition: all .28s ease-out;
-            margin-left: 180px;
-        }
-    }
+@import "src/styles/mixin.scss";
+.app-wrapper {
+  @include clearfix;
+  position: relative;
+  height: 100%;
+  width: 100%;
+}
 </style>

+ 38 - 43
src/views/layout/components/Navbar.vue

@@ -51,49 +51,44 @@ export default {
 </script>
 
 <style rel="stylesheet/scss" lang="scss" scoped>
-    .navbar {
-        height: 50px;
-        line-height: 50px;
-        border-radius: 0px !important;
-        .hamburger-container {
-            line-height: 58px;
-            height: 50px;
-            float: left;
-            padding: 0 10px;
-        }
-        .errLog-container {
-            display: inline-block;
-            position: absolute;
-            right: 150px;
-        }
-        .screenfull {
-            position: absolute;
-            right: 90px;
-            top: 16px;
-            color: red;
-        }
-        .avatar-container {
-            height: 50px;
-            display: inline-block;
-            position: absolute;
-            right: 35px;
-            .avatar-wrapper {
-                cursor: pointer;
-                margin-top: 5px;
-                position: relative;
-                .user-avatar {
-                    width: 40px;
-                    height: 40px;
-                    border-radius: 10px;
-                }
-                .el-icon-caret-bottom {
-                    position: absolute;
-                    right: -20px;
-                    top: 25px;
-                    font-size: 12px;
-                }
-            }
-        }
+.navbar {
+  height: 50px;
+  line-height: 50px;
+  border-radius: 0px !important;
+  .hamburger-container {
+    line-height: 58px;
+    height: 50px;
+    float: left;
+    padding: 0 10px;
+  }
+  .screenfull {
+    position: absolute;
+    right: 90px;
+    top: 16px;
+    color: red;
+  }
+  .avatar-container {
+    height: 50px;
+    display: inline-block;
+    position: absolute;
+    right: 35px;
+    .avatar-wrapper {
+      cursor: pointer;
+      margin-top: 5px;
+      position: relative;
+      .user-avatar {
+        width: 40px;
+        height: 40px;
+        border-radius: 10px;
+      }
+      .el-icon-caret-bottom {
+        position: absolute;
+        right: -20px;
+        top: 25px;
+        font-size: 12px;
+      }
     }
+  }
+}
 </style>
 

+ 18 - 21
src/views/layout/components/Sidebar/SidebarItem.vue

@@ -1,24 +1,32 @@
 <template>
-  <div>
+  <div class='menu-wrapper'>
     <template v-for="item in routes">
-      <router-link v-if="!item.hidden&&item.noDropdown&&item.children.length>0" :to="item.path+'/'+item.children[0].path">
-        <el-menu-item :index="item.path+'/'+item.children[0].path">
-          <svg-icon v-if='item.icon' :icon-class="item.icon" /> {{item.children[0].name}}
+
+      <router-link v-if="!item.hidden&&item.children&&item.children.length===1" :to="item.path+'/'+item.children[0].path" :key='item.children[0].name'>
+        <el-menu-item :index="item.path+'/'+item.children[0].path" class='submenu-title-noDropdown'>
+          <svg-icon v-if='item.children[0].meta&&item.children[0].meta.icon' :icon-class="item.children[0].meta.icon"></svg-icon>
+          <span v-if='item.children[0].meta&&item.children[0].meta.title'>{{item.children[0].meta.title}}</span>
         </el-menu-item>
       </router-link>
-      <el-submenu :index="item.name" v-if="!item.noDropdown&&!item.hidden">
+
+      <el-submenu v-if="!item.hidden&&item.children&&item.children.length>1" :index="item.name||item.path" :key='item.name'>
         <template slot="title">
-          <svg-icon v-if='item.icon' :icon-class="item.icon" /> {{item.name}}
+          <svg-icon v-if='item.meta&&item.meta.icon' :icon-class="item.meta.icon"></svg-icon>
+          <span v-if='item.meta&&item.meta.title'>{{item.meta.title}}</span>
         </template>
-        <template v-for="child in item.children" v-if='!child.hidden'>
-          <sidebar-item class='menu-indent' v-if='child.children&&child.children.length>0' :routes='[child]'> </sidebar-item>
-          <router-link v-else class="menu-indent" :to="item.path+'/'+child.path">
+
+        <template v-if='!child.hidden' v-for="child in item.children">
+          <sidebar-item class='nest-menu' v-if='child.children&&child.children.length>0' :routes='[child]' :key='child.path'></sidebar-item>
+
+          <router-link v-else :to="item.path+'/'+child.path" :key='child.name'>
             <el-menu-item :index="item.path+'/'+child.path">
-              {{child.name}}
+              <svg-icon v-if='child.meta&&child.meta.icon' :icon-class="child.meta.icon"></svg-icon>
+              <span v-if='child.meta&&child.meta.title'>{{child.meta.title}}</span>
             </el-menu-item>
           </router-link>
         </template>
       </el-submenu>
+
     </template>
   </div>
 </template>
@@ -33,14 +41,3 @@ export default {
   }
 }
 </script>
-
-<style rel="stylesheet/scss" lang="scss" scoped>
-.svg-icon {
-  margin-right: 10px;
-}
-.hideSidebar .menu-indent{
-  display: block;
-  text-indent: 10px;
-}
-</style>
-

+ 15 - 10
src/views/layout/components/Sidebar/index.vue

@@ -1,23 +1,28 @@
 <template>
-  <el-menu mode="vertical" theme="dark" :default-active="$route.path">
-    <sidebar-item :routes="routes"></sidebar-item>
-  </el-menu>
+  <scroll-bar>
+    <el-menu mode="vertical" unique-opened :default-active="$route.path" :collapse="isCollapse" background-color="#304156" text-color="#fff" active-text-color="#409EFF">
+      <sidebar-item :routes='routes'></sidebar-item>
+    </el-menu>
+  </scroll-bar>
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
 import SidebarItem from './SidebarItem'
+import ScrollBar from '@/components/ScrollBar'
+
 export default {
-  components: { SidebarItem },
+  components: { SidebarItem, ScrollBar },
   computed: {
+    ...mapGetters([
+      'sidebar'
+    ]),
     routes() {
       return this.$router.options.routes
+    },
+    isCollapse() {
+      return !this.sidebar.opened
     }
   }
 }
 </script>
-
-<style rel="stylesheet/scss" lang="scss" scoped>
-.el-menu {
-  min-height: 100%;
-}
-</style>