Browse Source

feat: 🎸 重构sider组件中

chenkl 4 years ago
parent
commit
51313d7116

+ 16 - 20
src/components/Hamburger/index.vue

@@ -1,27 +1,23 @@
 <template>
   <div>
-    <menu-unfold-outlined
-      v-if="collapsed"
-      class="trigger"
+    <svg
+      :class="{'is-active': !collapsed}"
+      class="hamburger"
+      viewBox="0 0 1024 1024"
+      xmlns="http://www.w3.org/2000/svg"
+      width="64"
+      height="64"
       @click="toggleCollapsed(!collapsed)"
-    />
-    <menu-fold-outlined
-      v-else
-      class="trigger"
-      @click="toggleCollapsed(!collapsed)"
-    />
+    >
+      <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 lang="ts">
 import { defineComponent, PropType } from 'vue'
-// import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue'
 export default defineComponent({
   name: 'Hamburger',
-  // components: {
-  //   MenuUnfoldOutlined,
-  //   MenuFoldOutlined
-  // },
   props: {
     collapsed: {
       type: Boolean as PropType<boolean>,
@@ -43,13 +39,13 @@ export default defineComponent({
 </script>
 
 <style lang="less" scoped>
-.trigger {
+.hamburger {
   display: inline-block;
-  transition: color 0.3s;
-  height: @navbarHeight;
-  line-height: @navbarHeight;
+  cursor: pointer;
+  width: 20px;
+  height: 20px;
 }
-.trigger:hover {
-  color: #1890ff;
+.hamburger.is-active {
+  transform: rotate(180deg);
 }
 </style>

+ 1 - 1
src/components/Logo/index.vue

@@ -50,7 +50,7 @@ export default defineComponent({
   padding-left: 18px;
   cursor: pointer;
   height: @topSilderHeight;
-  max-width: 200px;
+  width: 100%;
   img {
     width: 37px;
     height: 37px;

+ 34 - 0
src/components/Sider/Item.vue

@@ -0,0 +1,34 @@
+<template>
+  <div>
+    <i v-if="icon.includes('el-icon')" :class="[icon, 'sub-el-icon', 'anticon']" />
+    <svg-icon v-else :icon-class="icon" class="anticon" />
+    <slot name="title">
+      <span class="anticon-item">{{ title }}</span>
+    </slot>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType } from 'vue'
+
+export default defineComponent({
+  name: 'Item',
+  props: {
+    icon: {
+      type: String as PropType<string>,
+      default: ''
+    },
+    title: {
+      type: String as PropType<string>,
+      default: ''
+    }
+  }
+})
+</script>
+
+<style lang="less" scoped>
+.anticon-item {
+  opacity: 1;
+  transition: opacity .3s cubic-bezier(.645,.045,.355,1),width .3s cubic-bezier(.645,.045,.355,1);
+}
+</style>

+ 27 - 0
src/components/Sider/Link.vue

@@ -0,0 +1,27 @@
+<template>
+  <a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
+    <slot />
+  </a>
+  <router-link v-else :to="to">
+    <slot />
+  </router-link>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType } from 'vue'
+import { isExternal } from '@/utils/validate'
+
+export default defineComponent({
+  props: {
+    to: {
+      type: String as PropType<string>,
+      required: true
+    }
+  },
+  setup() {
+    return {
+      isExternal
+    }
+  }
+})
+</script>

+ 86 - 3
src/components/Sider/SiderItem.vue

@@ -1,11 +1,94 @@
 <template>
-  <div />
+  <template v-if="!item.meta?.hidden">
+    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.meta?.alwaysShow">
+      <el-menu-item :index="resolvePath(onlyOneChild.path)">
+        <item v-if="onlyOneChild.meta" :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" />
+        <template #title>
+          {{ onlyOneChild.meta.title }}
+        </template>
+      </el-menu-item>
+    </template>
+
+    <el-submenu v-else :index="resolvePath(item.path)">
+      <template #title>
+        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
+      </template>
+      <sider-item
+        v-for="child in item.children"
+        :key="child.path"
+        :is-nest="true"
+        :item="child"
+        :base-path="resolvePath(child.path)"
+      />
+    </el-submenu>
+  </template>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue'
+import { defineComponent, PropType, ref } from 'vue'
+import type { RouteRecordRaw } from 'vue-router'
+import path from 'path'
+import { isExternal } from '@/utils/validate'
+import Item from './Item.vue'
+import AppLink from './Link.vue'
 export default defineComponent({
-  name: 'SilderItem'
+  name: 'SiderItem',
+  components: { Item, AppLink },
+  props: {
+    // route object
+    item: {
+      type: Object as PropType<object>,
+      required: true
+    },
+    isNest: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    basePath: {
+      type: String as PropType<string>,
+      default: ''
+    }
+  },
+  setup(props) {
+    const onlyOneChild = ref<any>(null)
+
+    function hasOneShowingChild(children: RouteRecordRaw[] = [], parent: RouteRecordRaw): boolean {
+      const showingChildren: RouteRecordRaw[] = children.filter((item: RouteRecordRaw) => {
+        if (item.meta && item.meta.hidden) {
+          return false
+        } else {
+          // Temp set(will be used if only has one showing child)
+          onlyOneChild.value = item
+          return true
+        }
+      })
+
+      // When there is only one child router, the child router is displayed by default
+      if (showingChildren.length === 1) {
+        return true
+      }
+
+      // Show parent if there are no child router to display
+      if (showingChildren.length === 0) {
+        onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
+        return true
+      }
+
+      return false
+    }
+
+    function resolvePath(routePath: string): string {
+      if (isExternal(routePath)) {
+        return routePath
+      }
+      return path.resolve(props.basePath, routePath)
+    }
+    return {
+      onlyOneChild,
+      hasOneShowingChild,
+      resolvePath
+    }
+  }
 })
 </script>
 

+ 53 - 25
src/components/Sider/index.vue

@@ -1,21 +1,27 @@
 <template>
-  <div :class="{'has-logo':show_logo}">
-    <el-scrollbar wrap-class="scrollbar-wrapper">
-      <el-menu
-        :default-active="activeMenu"
-        :collapse="collapsed"
-        :unique-opened="false"
-        mode="vertical"
-      >
-        <sider-item
-          v-for="route in routers"
-          :key="route.path"
-          :item="route"
-          :base-path="route.path"
-        />
-      </el-menu>
-    </el-scrollbar>
-  </div>
+  <!-- <div :class="{'has-logo': showLogo}" class="sidebar-container"> -->
+  <!-- <el-scrollbar class="menu-wrap"> -->
+  <el-menu
+    :default-active="activeMenu"
+    :collapse="collapsed"
+    :unique-opened="false"
+    :background-color="variables.menuBg"
+    :text-color="variables.menuText"
+    :active-text-color="variables.menuActiveText"
+    mode="vertical"
+    class="sidebar-container"
+    :class="{'sidebar__wrap--collapsed': collapsed}"
+    @select="selectMenu"
+  >
+    <sider-item
+      v-for="route in routers"
+      :key="route.path"
+      :item="route"
+      :base-path="route.path"
+    />
+  </el-menu>
+  <!-- </el-scrollbar> -->
+  <!-- </div> -->
 </template>
 
 <script lang="ts">
@@ -25,9 +31,8 @@ import { permissionStore } from '_p/index/store/modules/permission'
 import { appStore } from '_p/index/store/modules/app'
 import type { RouteRecordRaw, RouteLocationNormalizedLoaded } from 'vue-router'
 import SiderItem from './SiderItem.vue'
-// import variables from '@/styles/variables.less'
-import config from '_p/index/config'
-const { show_logo } = config
+import variables from '@/styles/variables.less'
+import { isExternal } from '@/utils/validate'
 
 export default defineComponent({
   components: { SiderItem },
@@ -45,12 +50,23 @@ export default defineComponent({
       return path
     })
     const collapsed = computed(() => appStore.collapsed)
+    const showLogo = computed(() => appStore.showLogo)
+
+    function selectMenu(path: string) {
+      if (isExternal(path)) {
+        window.open(path)
+      } else {
+        push(path)
+      }
+    }
 
     return {
       routers,
       activeMenu,
       collapsed,
-      show_logo
+      showLogo,
+      variables,
+      selectMenu
     }
   }
 })
@@ -59,12 +75,24 @@ export default defineComponent({
 <style lang="less" scoped>
 .sidebar-container {
   height: 100%;
+  background: @menuBg;
+  @{deep}(.svg-icon) {
+    margin-right: 16px;
+  }
 }
 .has-logo {
   height: calc(~"100% - @{topSilderHeight}");
 }
-.menu-wrap {
-  height: 100%;
-  overflow: hidden;
-}
+// .menu-wrap {
+//   height: 100%;
+//   overflow: hidden;
+//   @{deep}(.el-scrollbar__wrap) {
+//     overflow-x: hidden;
+//     overflow-y: auto;
+//   }
+//   @{deep}(.el-menu) {
+//     border-right: none;
+//     width: 100%;
+//   }
+// }
 </style>

+ 2 - 2
src/pages/index/layout/components/Silder/Item.vue

@@ -1,7 +1,7 @@
 <template>
   <div>
-    <!-- <i v-if="icon.includes('el-icon')" :class="[icon, 'sub-el-icon', 'anticon']" /> -->
-    <svg-icon :icon-class="icon" class="anticon" />
+    <i v-if="icon.includes('el-icon')" :class="[icon, 'sub-el-icon', 'anticon']" />
+    <svg-icon v-else :icon-class="icon" class="anticon" />
     <slot name="title">
       <span class="anticon-item">{{ title }}</span>
     </slot>

+ 56 - 21
src/pages/index/layout/modules/Test.vue

@@ -1,25 +1,29 @@
 <template>
-  <div class="app__wrap">
-    <div class="sidebar__wrap">
+  <el-container class="app__wrap">
+    <div class="sidebar__wrap" :class="{'sidebar__wrap--collapsed': collapsed}">
       <logo
-        v-if="show_logo"
+        v-if="showLogo"
         :collapsed="collapsed"
       />
-      <sider /></div>
+      <sider />
+    </div>
+    <el-main><hamburger :collapsed="collapsed" class="hamburger-container" @toggleClick="setCollapsed" /></el-main>
+  </el-container>
+  <!-- <div class="app__wrap">
+    <div class="sidebar__wrap" :class="{'sidebar__wrap--collapsed': collapsed}">
+      <logo
+        v-if="showLogo"
+        :collapsed="collapsed"
+      />
+      <sider />
+    </div>
     <div class="main__wrap">
-      <div class="navbar__wrap" />
+      <div class="navbar__wrap">
+      </div>
       <div class="tags__wrap" />
       <div class="main__wrap" />
     </div>
-    <!-- <sidebar class="sidebar-wrap" />
-    <div :class="{hasTagsView: has_tags}" class="main-wrap">
-      <div>
-        <navbar />
-        <tags-view v-if="has_tags" />
-      </div>
-      <app-main />
-    </div> -->
-  </div>
+  </div> -->
 </template>
 
 <script lang="ts">
@@ -31,32 +35,63 @@ import TagsView from '../components/TagsView.vue'
 import Logo from '_c/Logo/index.vue'
 import Scrollbar from '_c/Scrollbar/index.vue'
 import Sider from '_c/Sider/index.vue'
-import Navbar from '../components/Navbar.vue'
-
-import config from '_p/index/config'
-const { show_logo, has_tags } = config
+import Hamburger from '_c/Hamburger/index.vue'
 export default defineComponent({
   name: 'Layout',
   components: {
     Sider,
-    Navbar,
+    Hamburger,
+    // Navbar,
     AppMain,
     TagsView,
     Logo,
     Scrollbar
   },
   setup() {
+    const collapsed = computed(() => appStore.collapsed)
+    const showLogo = computed(() => appStore.showLogo)
+    const showTags = computed(() => appStore.showTags)
+
+    function setCollapsed(collapsed: boolean): void {
+      appStore.SetCollapsed(collapsed)
+    }
+
     return {
-      show_logo, has_tags
+      collapsed,
+      showLogo,
+      showTags,
+      setCollapsed
     }
   }
 })
 </script>
 
 <style lang="less" scoped>
-.app-wrap {
+.app__wrap {
   position: relative;
   height: 100%;
   width: 100%;
+  // .sidebar__wrap {
+  //   position: absolute;
+  //   width: @menuWidth;
+  //   top: 0;
+  //   left: 0;
+  //   height: 100%;
+  //   transition: all 0.2s;
+  // }
+  // .sidebar__wrap--collapsed {
+  //   width: @menuMinWidth;
+
+  // }
+  // .main__wrap {
+  //   position: absolute;
+  //   width: calc(~"100% - @{menuWidth}");
+  //   height: 100%;
+  //   top: 0;
+  //   left: @menuWidth;
+  //   .navbar__wrap {
+  //     height: @navbarHeight;
+  //   }
+  // }
 }
 </style>

+ 59 - 59
src/pages/index/router/index.ts

@@ -357,65 +357,65 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
   //     }
   //   ]
   // },
-  // {
-  //   path: '/level',
-  //   component: Layout,
-  //   redirect: '/level/menu1/menu1-1/menu1-1-1',
-  //   name: 'Level',
-  //   meta: {
-  //     title: '多级菜单缓存',
-  //     icon: 'nested'
-  //   },
-  //   children: [
-  //     {
-  //       path: 'menu1',
-  //       name: 'Menu1Demo',
-  //       component: getParentLayout('Menu1Demo'),
-  //       redirect: '/level/menu1/menu1-1/menu1-1-1',
-  //       meta: {
-  //         title: 'Menu1'
-  //       },
-  //       children: [
-  //         {
-  //           path: 'menu1-1',
-  //           name: 'Menu11Demo',
-  //           component: getParentLayout('Menu11Demo'),
-  //           redirect: '/level/menu1/menu1-1/menu1-1-1',
-  //           meta: {
-  //             title: 'Menu1-1',
-  //             alwaysShow: true
-  //           },
-  //           children: [
-  //             {
-  //               path: 'menu1-1-1',
-  //               name: 'Menu111Demo',
-  //               component: () => import('_p/index/views/level/Menu111.vue'),
-  //               meta: {
-  //                 title: 'Menu1-1-1'
-  //               }
-  //             }
-  //           ]
-  //         },
-  //         {
-  //           path: 'menu1-2',
-  //           name: 'Menu12Demo',
-  //           component: () => import('_p/index/views/level/Menu12.vue'),
-  //           meta: {
-  //             title: 'Menu1-2'
-  //           }
-  //         }
-  //       ]
-  //     },
-  //     {
-  //       path: 'menu2',
-  //       name: 'Menu2Demo',
-  //       component: () => import('_p/index/views/level/Menu2.vue'),
-  //       meta: {
-  //         title: 'Menu2'
-  //       }
-  //     }
-  //   ]
-  // },
+  {
+    path: '/level',
+    component: Layout,
+    redirect: '/level/menu1/menu1-1/menu1-1-1',
+    name: 'Level',
+    meta: {
+      title: '多级菜单缓存',
+      icon: 'nested'
+    },
+    children: [
+      {
+        path: 'menu1',
+        name: 'Menu1Demo',
+        component: getParentLayout('Menu1Demo'),
+        redirect: '/level/menu1/menu1-1/menu1-1-1',
+        meta: {
+          title: 'Menu1'
+        },
+        children: [
+          {
+            path: 'menu1-1',
+            name: 'Menu11Demo',
+            component: getParentLayout('Menu11Demo'),
+            redirect: '/level/menu1/menu1-1/menu1-1-1',
+            meta: {
+              title: 'Menu1-1',
+              alwaysShow: true
+            },
+            children: [
+              {
+                path: 'menu1-1-1',
+                name: 'Menu111Demo',
+                component: () => import('_p/index/views/level/Menu111.vue'),
+                meta: {
+                  title: 'Menu1-1-1'
+                }
+              }
+            ]
+          },
+          {
+            path: 'menu1-2',
+            name: 'Menu12Demo',
+            component: () => import('_p/index/views/level/Menu12.vue'),
+            meta: {
+              title: 'Menu1-2'
+            }
+          }
+        ]
+      },
+      {
+        path: 'menu2',
+        name: 'Menu2Demo',
+        component: () => import('_p/index/views/level/Menu2.vue'),
+        meta: {
+          title: 'Menu2'
+        }
+      }
+    ]
+  },
   // {
   //   path: '/example-demo',
   //   component: Layout,

+ 1 - 1
src/pages/index/store/modules/app.ts

@@ -14,7 +14,7 @@ export interface AppState {
 @Module({ dynamic: true, namespaced: true, store, name: 'app' })
 class App extends VuexModule implements AppState {
   public collapsed = false // 菜单栏是否栏缩收
-  public showLogo = true // 是否显示logo
+  public showLogo = false // 是否显示logo
   public showTags = true // 是否显示标签栏
   public showNavbar = true // 是否显示navbar
   public fixedTags = true // 是否固定标签栏

+ 1 - 1
src/pages/index/views/level/Menu111.vue

@@ -1,7 +1,7 @@
 <template>
   <div style="padding: 20px; background: #fff;display: flex;align-items: center;">
     <div style="min-width: 200px;">多层级缓存-页面1-1-1:</div>
-    <a-input />
+    <el-input />
   </div>
 </template>
 

+ 1 - 1
src/pages/index/views/level/Menu12.vue

@@ -1,7 +1,7 @@
 <template>
   <div style="padding: 20px; background: #fff;display: flex;align-items: center;">
     <div style="min-width: 200px;">多层级缓存-页面1-2:</div>
-    <a-input />
+    <el-input />
   </div>
 </template>
 

+ 1 - 1
src/pages/index/views/level/Menu2.vue

@@ -1,7 +1,7 @@
 <template>
   <div style="padding: 20px; background: #fff;display: flex;align-items: center;">
     <div style="min-width: 200px;">多层级缓存-页面2:</div>
-    <a-input />
+    <el-input />
   </div>
 </template>
 

+ 1 - 0
src/styles/index.less

@@ -1,4 +1,5 @@
 @import '~element-plus/lib/theme-chalk/index.css';
+// @import './sidebar.less';
 @import './transition.less';
 @import './silder.less';
 @import './glob.less';

+ 234 - 0
src/styles/sidebar.less

@@ -0,0 +1,234 @@
+#app {
+
+  // 主体区域 Main container
+  .main-container {
+    min-height: 100%;
+    transition: margin-left .28s;
+    margin-left: @menuWidth;
+    position: relative;
+  }
+
+  // 侧边栏 Sidebar container
+  .sidebar-container {
+    transition: width 0.28s;
+    width: @menuWidth !important;
+    height: 100%;
+    position: fixed;
+    font-size: 0px;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1001;
+    overflow: hidden;
+
+    //reset element-ui css
+    .horizontal-collapse-transition {
+      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+    }
+
+    .scrollbar-wrapper {
+      overflow-x: hidden !important;
+
+      .el-scrollbar__view {
+        height: 100%;
+      }
+    }
+
+    .el-scrollbar__bar.is-vertical {
+      right: 0px;
+    }
+    
+    .el-scrollbar {
+      height: 100%;
+    }
+    
+    &.has-logo {
+      .el-scrollbar {
+        height: calc(100% - 70px);
+      }
+    }
+
+    .is-horizontal {
+      display: none;
+    }
+
+    a {
+      display: inline-block;
+      width: 100%;
+      overflow: hidden;
+    }
+
+    .svg-icon {
+      margin-right: 16px;
+    }
+
+    .el-menu {
+      border: none;
+      height: 100%;
+      width: 100% !important;
+    }
+
+    // menu hover
+    .submenu-title-noDropdown,
+    .el-submenu__title {
+      color: hsla(0,0%,100%,.7) !important;
+      &:hover {
+        // background-color: @menuHover !important;
+        color: @subMenuActiveText !important;
+      }
+    }
+    
+    .is-active>.el-submenu__title {
+      color: @subMenuActiveText !important;
+    }
+
+    .is-active {
+      color: @subMenuActiveText !important;
+      background-color: @menuActiveBg !important;
+      &:hover {
+        color: @subMenuActiveText !important;
+        background-color: @menuActiveBg !important;
+      }
+      & .el-menu-item {
+        background-color: @menuActiveBg !important;
+        &:hover {
+          color: @subMenuActiveText !important;
+        }
+      }
+    }
+
+    & .nest-menu .el-submenu>.el-submenu__title,
+    & .el-submenu .el-menu-item {
+      min-width: @menuWidth !important;
+      background-color: @subMenuBg !important;
+
+      &:hover {
+        color: @subMenuActiveText !important;
+        background-color: @subMenuHover !important;
+      }
+    }
+    
+    & .nest-menu {
+      & .is-active {
+        background-color: @menuActiveBg !important;
+        &:hover {
+          color: @subMenuActiveText !important;
+          background-color: @menuActiveBg !important;
+        }
+      }
+    }
+  }
+
+  .hideSidebar {
+    .sidebar-container {
+      width: 36px !important;
+    }
+
+    .main-container {
+      margin-left: 36px;
+    }
+
+    .submenu-title-noDropdown {
+      padding-left: 10px !important;
+      position: relative;
+
+      .el-tooltip {
+        padding: 0 10px !important;
+      }
+    }
+
+    .el-submenu {
+      overflow: hidden;
+
+      &>.el-submenu__title {
+        padding-left: 10px !important;
+
+        .el-submenu__icon-arrow {
+          display: none;
+        }
+      }
+    }
+
+    .el-menu--collapse {
+      .el-submenu {
+        &>.el-submenu__title {
+          &>span {
+            height: 0;
+            width: 0;
+            overflow: hidden;
+            visibility: hidden;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .el-menu--collapse .el-menu .el-submenu {
+    min-width: @menuWidth !important;
+  }
+
+  // 适配移动端, Mobile responsive
+  .mobile {
+    .main-container {
+      margin-left: 0px;
+    }
+
+    .sidebar-container {
+      transition: transform .28s;
+      width: @menuWidth !important;
+    }
+
+    &.hideSidebar {
+      .sidebar-container {
+        pointer-events: none;
+        transition-duration: 0.3s;
+        transform: translate3d(-@menuWidth, 0, 0);
+      }
+    }
+  }
+
+  .withoutAnimation {
+
+    .main-container,
+    .sidebar-container {
+      transition: none;
+    }
+  }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+  &>.el-menu {
+    .svg-icon {
+      margin-right: 16px;
+    }
+  }
+
+  .nest-menu .el-submenu>.el-submenu__title,
+  .el-menu-item {
+    &:hover {
+      // you can use @subMenuHover
+      // background-color: @menuHover !important;
+    }
+  }
+
+  // the scroll bar appears when the subMenu is too long
+  >.el-menu--popup {
+    max-height: 100vh;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar-track-piece {
+      background: #d3dce6;
+    }
+
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #99a9bf;
+      border-radius: 20px;
+    }
+  }
+}

+ 26 - 4
src/styles/variables.less

@@ -1,15 +1,25 @@
 // Silder
+@menuText: #bfcbd9;
+@menuActiveText: #409EFF;
+@menuActiveBg: #2d8cf0;
+
+@menuBg: #001529;
+
+@subMenuBg: #1f2d3d;
+@subMenuHover: #1f2d3d;
+@subMenuActiveText: #fff;
+
+@menuWidth: 200px;
+@menuMinWidth: 64px;
+
 @menuLightActiveText: #1890ff;
 @menuLightActiveBg: #e6f7ff;
 
 @menuDarkActiveText: #fff;
 @menuDarkActiveBg: #1890ff;
 
-@menuBg: #001529;
 @menuLightBg: #fff;
 
-@menuWidth: 200px;
-
 // topSilder
 @topSilderHeight: 50px;
 
@@ -26,4 +36,16 @@
 @minWidth: 992px;
 
 // deep
-@deep: ~'::v-deep';
+@deep: ~'::v-deep';
+
+// the :export directive is the magic sauce for webpack
+:export {
+  menuText: @menuText;
+  menuActiveText: @menuActiveText;
+  menuActiveBg: @menuActiveBg;
+  menuBg: @menuBg;
+  subMenuBg: @subMenuBg;
+  subMenuHover: @subMenuHover;
+  menuWidth: @menuWidth;
+  menuMinWidth: @menuMinWidth;
+}

+ 14 - 0
src/styles/variables.less.d.ts

@@ -0,0 +1,14 @@
+export interface IScssVariables {
+  menuText: string
+  menuActiveText: string
+  menuActiveBg: string
+  menuBg: string
+  subMenuBg: string
+  subMenuHover: string
+  menuWidth: string
+  menuMinWidth: string
+}
+
+export const variables: IScssVariables
+
+export default variables