浏览代码

feat: 🎸 新增固定一级菜单配置

陈凯龙 4 年之前
父节点
当前提交
4c4903e806

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "vue-element-plus-admin",
-  "version": "0.0.5",
+  "version": "0.0.6",
   "private": true,
   "scripts": {
     "serve": "vue-cli-service serve",

+ 0 - 0
src/pages/index/layout/components/AppMain.vue → src/pages/index/layout/components/AppMain/index.vue


+ 0 - 0
src/components/Backtop/index.vue → src/pages/index/layout/components/Backtop/index.vue


+ 0 - 0
src/components/Breadcrumb/Breadcrumb.vue → src/pages/index/layout/components/Breadcrumb/Breadcrumb.vue


+ 0 - 0
src/components/Breadcrumb/BreadcrumbItem.vue → src/pages/index/layout/components/Breadcrumb/BreadcrumbItem.vue


+ 12 - 12
src/components/Breadcrumb/index.vue → src/pages/index/layout/components/Breadcrumb/index.vue

@@ -36,23 +36,23 @@ export default defineComponent({
     const levelList = ref<RouteRecordRaw[]>([])
 
     function getBreadcrumb() {
-      let matched: any[] = currentRoute.value.matched.filter((item: RouteLocationMatched) => item.meta && item.meta.title)
-      const first = matched[0]
+      const matched: any[] = currentRoute.value.matched.filter((item: RouteLocationMatched) => item.meta && item.meta.title)
+      // const first = matched[0]
 
-      if (!isDashboard(first)) {
-        matched = [{ path: '/dashboard', meta: { title: '首页', icon: 'dashboard' }}].concat(matched)
-      }
+      // if (!isDashboard(first)) {
+      //   matched = [{ path: '/dashboard', meta: { title: '首页', icon: 'dashboard' }}].concat(matched)
+      // }
 
       levelList.value = matched.filter((item: RouteLocationMatched) => item.meta && item.meta.title && item.meta.breadcrumb !== false)
     }
 
-    function isDashboard(route: RouteLocationMatched) {
-      const name = route && route.name
-      if (!name) {
-        return false
-      }
-      return (name as any).trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
-    }
+    // function isDashboard(route: RouteLocationMatched) {
+    //   const name = route && route.name
+    //   if (!name) {
+    //     return false
+    //   }
+    //   return (name as any).trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
+    // }
 
     function pathCompile(path: string): string {
       const { params } = currentRoute.value

+ 0 - 0
src/components/Hamburger/index.vue → src/pages/index/layout/components/Hamburger/index.vue


+ 0 - 0
src/components/Logo/index.vue → src/pages/index/layout/components/Logo/index.vue


+ 125 - 0
src/pages/index/layout/components/MenuTab/index.vue

@@ -0,0 +1,125 @@
+<template>
+  <el-tabs
+    v-model="activeName"
+    :tab-position="tabPosition"
+    @tab-click="changeTab"
+  >
+    <el-tab-pane
+      v-for="(item, $index) in tabRouters"
+      :key="$index"
+      :name="item.path === '/' ? '/dashboard' : item.path"
+    >
+      <template #label>
+        <div class="label-item">
+          <svg-icon :icon-class="filterTab(item, 'icon')" />
+          <div class="title-item">{{ filterTab(item, 'title') }}</div>
+        </div>
+      </template>
+    </el-tab-pane>
+  </el-tabs>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref, watch, onMounted, computed } from 'vue'
+import { appStore } from '_@/store/modules/app'
+import { permissionStore } from '_@/store/modules/permission'
+import type { RouteRecordRaw } from 'vue-router'
+import { useRouter } from 'vue-router'
+import { findIndex } from '@/utils'
+import { isExternal } from '@/utils/validate'
+
+export default defineComponent({
+  name: 'MenuTab',
+  setup() {
+    const { currentRoute, push } = useRouter()
+
+    const activeName = ref<string>('')
+
+    const routers = computed((): RouteRecordRaw[] => permissionStore.routers)
+    const tabRouters = computed((): RouteRecordRaw[] => routers.value.filter(v => !v.meta?.hidden))
+
+    const layout = computed(() => appStore.layout)
+    const tabPosition = computed(() => layout.value === 'Classic' ? 'left' : 'top')
+
+    function init() {
+      const currentPath = currentRoute.value.fullPath.split('/')
+      const index = findIndex(tabRouters.value, (v: RouteRecordRaw) => {
+        if (v.path === '/') {
+          return `/${currentPath[1]}` === '/dashboard'
+        } else {
+          return v.path === `/${currentPath[1]}`
+        }
+      })
+      if (index > -1) {
+        activeName.value = `/${currentPath[1]}`
+        setActive(index)
+        permissionStore.SetAcitveTab(activeName.value)
+      }
+    }
+
+    function filterTab(item: RouteRecordRaw | any, key: string): any {
+      return item.meta && item.meta[key] ? item.meta[key] : item.children[0].meta[key]
+    }
+
+    function setActive(index: number): void {
+      const currRoute: any = tabRouters.value[index]
+      permissionStore.SetMenuTabRouters(currRoute.children)
+    }
+
+    function changeTab(item: any) {
+      const currRoute: any = tabRouters.value[item.index]
+      permissionStore.SetMenuTabRouters(currRoute.children)
+      if (isExternal(currRoute.children[0].path)) {
+        window.open(currRoute.children[0].path)
+      } else {
+        push(`${activeName.value === '/dashboard' ? '' : activeName.value}/${currRoute.children[0].path}`)
+        permissionStore.SetAcitveTab(activeName.value)
+      }
+    }
+
+    onMounted(() => {
+      init()
+    })
+
+    watch(
+      () => currentRoute.value,
+      () => {
+        init()
+      }
+    )
+
+    watch(
+      () => activeName.value,
+      (val) => {
+        permissionStore.SetAcitveTab(val)
+      }
+    )
+
+    return {
+      activeName,
+      tabRouters,
+      tabPosition,
+      filterTab,
+      setActive,
+      changeTab
+    }
+  }
+})
+</script>
+
+<style lang="less" scoped>
+.label-item {
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  flex-wrap: wrap;
+  align-items: center;
+  &>div {
+    width: 100%;
+  }
+  .title-item {
+    position: relative;
+    top: -5px;
+  }
+}
+</style>

+ 0 - 0
src/components/Screenfull/index.vue → src/pages/index/layout/components/Screenfull/index.vue


+ 13 - 1
src/components/Setting/index.vue → src/pages/index/layout/components/Setting/index.vue

@@ -37,6 +37,11 @@
       <!-- <div class="setting__title">界面功能</div> -->
 
       <div class="setting__title">界面显示</div>
+      <div v-if="layout !== 'Top'" class="setting__item">
+        <span>固定一级菜单</span>
+        <el-switch v-model="showMenuTab" @change="setShowMenuTab" />
+      </div>
+
       <div class="setting__item">
         <span>固定Header</span>
         <el-switch v-model="fixedHeader" @change="setFixedHeader" />
@@ -117,6 +122,7 @@ export default defineComponent({
       if (mode === layout.value) return
       appStore.SetLayout(mode)
       appStore.SetCollapsed(false)
+      mode === 'Top' && appStore.SetShowMenuTab(false)
     }
 
     const fixedHeader = ref<boolean>(appStore.fixedHeader)
@@ -179,6 +185,11 @@ export default defineComponent({
       appStore.SetShowBackTop(showBackTop)
     }
 
+    const showMenuTab = ref<boolean>(appStore.showMenuTab)
+    function setShowMenuTab(showMenuTab: boolean) {
+      appStore.SetShowMenuTab(showMenuTab)
+    }
+
     return {
       drawer, toggleClick,
       layout, setLayout,
@@ -193,7 +204,8 @@ export default defineComponent({
       title, setTitle,
       logoTitle, setLogoTitle,
       greyMode, setGreyMode,
-      showBackTop, setShowBackTop
+      showBackTop, setShowBackTop,
+      showMenuTab, setShowMenuTab
     }
   }
 })

+ 0 - 0
src/components/Sider/Item.vue → src/pages/index/layout/components/Sider/Item.vue


+ 12 - 5
src/components/Sider/SiderItem.vue → src/pages/index/layout/components/Sider/SiderItem.vue

@@ -1,7 +1,7 @@
 <template>
   <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)" :class="{'submenu-title-noDropdown': !isNest}">
+      <el-menu-item :index="resolvePath(onlyOneChild.path, showMenuTab ? `${activeTab === '/dashboard' ? '' : activeTab}/${basePath}` : '')" :class="{'submenu-title-noDropdown': !isNest}">
         <item v-if="onlyOneChild.meta" :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
         <template #title>
           <span class="anticon-item">{{ onlyOneChild.meta.title }}</span>
@@ -14,7 +14,7 @@
       :popper-class="layout !== 'Top'
         ? 'nest-popper-menu'
         : 'top-popper-menu'"
-      :index="resolvePath(item.path)"
+      :index="resolvePath(item.path, showMenuTab ? `${activeTab === '/dashboard' ? '' : activeTab}/${basePath}` : '')"
     >
       <template #title>
         <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
@@ -32,11 +32,13 @@
 </template>
 
 <script lang="ts">
-import { defineComponent, PropType, ref } from 'vue'
+import { defineComponent, PropType, ref, computed } from 'vue'
 import type { RouteRecordRaw } from 'vue-router'
 import path from 'path'
 import { isExternal } from '@/utils/validate'
 import Item from './Item.vue'
+import { permissionStore } from '_@/store/modules/permission'
+import { appStore } from '_@/store/modules/app'
 export default defineComponent({
   name: 'SiderItem',
   components: { Item },
@@ -62,6 +64,9 @@ export default defineComponent({
   setup(props) {
     const onlyOneChild = ref<any>(null)
 
+    const activeTab = computed(() => permissionStore.activeTab)
+    const showMenuTab = computed(() => appStore.showMenuTab)
+
     function hasOneShowingChild(children: RouteRecordRaw[] = [], parent: RouteRecordRaw): boolean {
       const showingChildren: RouteRecordRaw[] = children.filter((item: RouteRecordRaw) => {
         if (item.meta && item.meta.hidden) {
@@ -87,14 +92,16 @@ export default defineComponent({
       return false
     }
 
-    function resolvePath(routePath: string): string {
+    function resolvePath(routePath: string, otherPath: string): string {
       if (isExternal(routePath)) {
         return routePath
       }
-      return path.resolve(props.basePath, routePath)
+      return path.resolve(otherPath || props.basePath, routePath)
     }
     return {
       onlyOneChild,
+      activeTab,
+      showMenuTab,
       hasOneShowingChild,
       resolvePath
     }

+ 9 - 1
src/components/Sider/index.vue → src/pages/index/layout/components/Sider/index.vue

@@ -12,7 +12,7 @@
         @select="selectMenu"
       >
         <sider-item
-          v-for="route in routers"
+          v-for="route in showMenuTab ? menuTabRouters : routers"
           :key="route.path"
           :item="route"
           :layout="layout"
@@ -62,7 +62,12 @@ export default defineComponent({
     const collapsed = computed(() => appStore.collapsed)
     const showLogo = computed(() => appStore.showLogo)
 
+    const showMenuTab = computed(() => appStore.showMenuTab)
+    const menuTabRouters = computed(() => permissionStore.menuTabRouters)
+    const activeTab = computed(() => permissionStore.activeTab)
+
     function selectMenu(path: string) {
+      if (currentRoute.value.fullPath === path) return
       if (isExternal(path)) {
         window.open(path)
       } else {
@@ -75,6 +80,9 @@ export default defineComponent({
       activeMenu,
       collapsed,
       showLogo,
+      showMenuTab,
+      menuTabRouters,
+      activeTab,
       variables,
       selectMenu
     }

+ 0 - 0
src/components/TagsView/ScrollPane.vue → src/pages/index/layout/components/TagsView/ScrollPane.vue


+ 0 - 0
src/components/TagsView/index.vue → src/pages/index/layout/components/TagsView/index.vue


+ 0 - 0
src/components/UserInfo/index.vue → src/pages/index/layout/components/UserInfo/index.vue


+ 27 - 15
src/pages/index/layout/modules/Classic.vue

@@ -1,10 +1,13 @@
 <template>
   <div :class="classObj" class="app__wrap">
     <!-- Classic -->
+    <div v-if="showMenuTab" class="menu__tab">
+      <menu-tab />
+    </div>
     <div
       id="sidebar__wrap"
       class="sidebar__wrap"
-      :class="{'sidebar__wrap--collapsed': collapsed}"
+      :class="{'sidebar__wrap--collapsed': collapsed, 'sidebar__wrap--tab': showMenuTab}"
     >
       <logo
         v-if="showLogo && layout === 'Classic'"
@@ -16,7 +19,9 @@
     <div
       class="main__wrap"
       :class="{
-        'main__wrap--collapsed': collapsed
+        'main__wrap--collapsed': collapsed,
+        'main__wrap--tab': showMenuTab,
+        'main__wrap--tab--collapsed': showMenuTab && collapsed
       }"
     >
       <el-scrollbar
@@ -31,7 +36,10 @@
           class="header__wrap"
           :class="{
             'header__wrap--fixed': fixedHeader,
-            'header__wrap--collapsed': fixedHeader && collapsed
+            'header__wrap--tab--fixed': fixedHeader && showMenuTab,
+            'header__wrap--collapsed': fixedHeader && collapsed,
+            'header__wrap--tab': showMenuTab,
+            'header__wrap--tab--collapsed': showMenuTab && collapsed
           }"
         >
           <div
@@ -75,17 +83,18 @@
 import { defineComponent, computed } from 'vue'
 import { appStore } from '_@/store/modules/app'
 
-import AppMain from '../components/AppMain.vue'
-import TagsView from '_c/TagsView/index.vue'
-import Logo from '_c/Logo/index.vue'
-import Sider from '_c/Sider/index.vue'
-import Hamburger from '_c/Hamburger/index.vue'
-import Breadcrumb from '_c/Breadcrumb/index.vue'
-import Screenfull from '_c/Screenfull/index.vue'
-import UserInfo from '_c/UserInfo/index.vue'
+import AppMain from '../components/AppMain/index.vue'
+import TagsView from '../components/TagsView/index.vue'
+import Logo from '../components/Logo/index.vue'
+import Sider from '../components/Sider/index.vue'
+import Hamburger from '../components/Hamburger/index.vue'
+import Breadcrumb from '../components/Breadcrumb/index.vue'
+import Screenfull from '../components/Screenfull/index.vue'
+import UserInfo from '../components/UserInfo/index.vue'
+import MenuTab from '../components/MenuTab/index.vue'
 
-import Setting from '_c/Setting/index.vue'
-import Backtop from '_c/Backtop/index.vue'
+import Setting from '../components/Setting/index.vue'
+import Backtop from '../components/Backtop/index.vue'
 export default defineComponent({
   name: 'Classic',
   components: {
@@ -98,7 +107,8 @@ export default defineComponent({
     TagsView,
     Logo,
     Setting,
-    Backtop
+    Backtop,
+    MenuTab
   },
   setup() {
     const layout = computed(() => appStore.layout)
@@ -114,6 +124,7 @@ export default defineComponent({
     // const fixedTags = computed(() => appStore.fixedTags)
     const fixedHeader = computed(() => appStore.fixedHeader)
     const showBackTop = computed(() => appStore.showBackTop)
+    const showMenuTab = computed(() => appStore.showMenuTab)
 
     const classObj = computed(() => {
       const obj = {}
@@ -140,7 +151,8 @@ export default defineComponent({
       // fixedNavbar,
       // fixedTags,
       setCollapsed,
-      showBackTop
+      showBackTop,
+      showMenuTab
     }
   }
 })

+ 18 - 14
src/pages/index/layout/modules/LeftTop.vue

@@ -22,8 +22,8 @@
           :collapsed="collapsed"
         />
       </div>
-      <div v-if="layout === 'Top'" class="sidebar__item--Top">
-        <sider :layout="layout" mode="horizontal" />
+      <div v-if="showMenuTab" class="menu__tab--top sidebar__item--Top">
+        <menu-tab />
       </div>
       <div>
         <div v-if="showScreenfull || showUserInfo" class="navbar__wrap--right">
@@ -96,17 +96,18 @@
 import { defineComponent, computed } from 'vue'
 import { appStore } from '_@/store/modules/app'
 
-import AppMain from '../components/AppMain.vue'
-import TagsView from '_c/TagsView/index.vue'
-import Logo from '_c/Logo/index.vue'
-import Sider from '_c/Sider/index.vue'
-import Hamburger from '_c/Hamburger/index.vue'
-import Breadcrumb from '_c/Breadcrumb/index.vue'
-import Screenfull from '_c/Screenfull/index.vue'
-import UserInfo from '_c/UserInfo/index.vue'
+import AppMain from '../components/AppMain/index.vue'
+import TagsView from '../components/TagsView/index.vue'
+import Logo from '../components/Logo/index.vue'
+import Sider from '../components/Sider/index.vue'
+import Hamburger from '../components/Hamburger/index.vue'
+import Breadcrumb from '../components/Breadcrumb/index.vue'
+import Screenfull from '../components/Screenfull/index.vue'
+import UserInfo from '../components/UserInfo/index.vue'
+import MenuTab from '../components/MenuTab/index.vue'
 
-import Setting from '_c/Setting/index.vue'
-import Backtop from '_c/Backtop/index.vue'
+import Setting from '../components/Setting/index.vue'
+import Backtop from '../components/Backtop/index.vue'
 export default defineComponent({
   name: 'LeftTop',
   components: {
@@ -119,7 +120,8 @@ export default defineComponent({
     TagsView,
     Logo,
     Setting,
-    Backtop
+    Backtop,
+    MenuTab
   },
   setup() {
     const layout = computed(() => appStore.layout)
@@ -135,6 +137,7 @@ export default defineComponent({
     // const fixedTags = computed(() => appStore.fixedTags)
     const fixedHeader = computed(() => appStore.fixedHeader)
     const showBackTop = computed(() => appStore.showBackTop)
+    const showMenuTab = computed(() => appStore.showMenuTab)
 
     const classObj = computed(() => {
       const obj = {}
@@ -161,7 +164,8 @@ export default defineComponent({
       // fixedNavbar,
       // fixedTags,
       setCollapsed,
-      showBackTop
+      showBackTop,
+      showMenuTab
     }
   }
 })

+ 10 - 10
src/pages/index/layout/modules/Top.vue

@@ -81,17 +81,17 @@
 import { defineComponent, computed } from 'vue'
 import { appStore } from '_@/store/modules/app'
 
-import AppMain from '../components/AppMain.vue'
-import TagsView from '_c/TagsView/index.vue'
-import Logo from '_c/Logo/index.vue'
-import Sider from '_c/Sider/index.vue'
-// import Hamburger from '_c/Hamburger/index.vue'
-// import Breadcrumb from '_c/Breadcrumb/index.vue'
-import Screenfull from '_c/Screenfull/index.vue'
-import UserInfo from '_c/UserInfo/index.vue'
+import AppMain from '../components/AppMain/index.vue'
+import TagsView from '../components/TagsView/index.vue'
+import Logo from '../components/Logo/index.vue'
+import Sider from '../components/Sider/index.vue'
+// import Hamburger from '../components/Hamburger/index.vue'
+// import Breadcrumb from '../components/Breadcrumb/index.vue'
+import Screenfull from '../components/Screenfull/index.vue'
+import UserInfo from '../components/UserInfo/index.vue'
 
-import Setting from '_c/Setting/index.vue'
-import Backtop from '_c/Backtop/index.vue'
+import Setting from '../components/Setting/index.vue'
+import Backtop from '../components/Backtop/index.vue'
 export default defineComponent({
   name: 'Top',
   components: {

+ 89 - 0
src/pages/index/layout/modules/style.less

@@ -2,6 +2,74 @@
   position: relative;
   height: 100%;
   width: 100%;
+  .menu__tab {
+    width: @menuTabWidth;
+    height: 100%;
+    background: @menuTabBg;
+    @{deep}(.is-left::after),
+    @{deep}(.el-tabs__active-bar) {
+      display: none;
+    }
+    @{deep}(.el-tabs) {
+      height: 100%;
+      .el-tabs__header {
+        height: 100%;
+        margin-right: 0;
+        width: @menuTabWidth;
+        .el-tabs__nav-wrap {
+          height: 100%;
+          .el-tabs__item {
+            padding: 0;
+            line-height: 0;
+            height: @menuTabItemHeight;
+            text-align: center;
+            color: @menuTabText;
+            transition: all .3s cubic-bezier(.645,.045,.355,1);
+          }
+          .el-tabs__item:hover {
+            color: @menuTabActiveText;
+            background: @menuTabActiveBg;
+          }
+          .is-active {
+            color: @menuTabActiveText;
+            background: @menuTabActiveBg;
+          }
+        }
+      }
+    }
+    &--top {
+      width: auto;
+      @{deep}(.el-tabs),
+      @{deep}(.is-top),
+      @{deep}(.el-tabs__nav-scroll) {
+        height: 100%;
+      }
+      @{deep}(.is-top::after),
+      @{deep}(.el-tabs__active-bar) {
+        display: none;
+      }
+      @{deep}(.is-top) {
+        margin-bottom: 0;
+      }
+      @{deep}(.el-tabs__item) {
+        padding: 0;
+        width: @menuTopTabWidth;
+        text-align: center;
+        height: 100%;
+        padding-top: 10px;
+        color: @menuTabText;
+        transition: all .3s cubic-bezier(.645,.045,.355,1);
+      }
+      @{deep}(.el-tabs__item:hover) {
+        color: @menuTopTabActiveText;
+        background: @menuTopTabActiveBg;
+      }
+      @{deep}(.is-active) {
+        color: @menuTopTabActiveText;
+        background: @menuTopTabActiveBg;
+      }
+    }
+  }
   .sidebar__wrap {
     position: fixed;
     width: @menuWidth;
@@ -10,6 +78,9 @@
     height: 100%;
     transition: width 0.2s;
   }
+  .sidebar__wrap--tab {
+    left: @menuTabWidth;
+  }
   .sidebar__wrap--collapsed {
     width: @menuMinWidth;
     @{deep}(.anticon-item) {
@@ -76,10 +147,18 @@
     }
     // content样式
   }
+  .main__wrap--tab {
+    width: calc(~"100% - @{menuWidth} - @{menuTabWidth}");
+    left: @menuWidth + @menuTabWidth;
+  }
   .main__wrap--collapsed {
     width: calc(~"100% - @{menuMinWidth}");
     left: @menuMinWidth;
   }
+  .main__wrap--tab--collapsed {
+    width: calc(~"100% - @{menuMinWidth} - @{menuTabWidth}");
+    left: @menuMinWidth + @menuTabWidth;
+  }
 }
 
 // LeftTop模式
@@ -105,10 +184,20 @@
     left: @menuWidth !important;
     z-index: 200;
   }
+  .header__wrap--tab--fixed {
+    width: calc(~"100% - @{menuWidth} - @{menuTabWidth}") !important;
+  }
+  .header__wrap--tab {
+    left: @menuWidth + @menuTabWidth !important;
+  }
   .header__wrap--collapsed {
     width: calc(~"100% - @{menuMinWidth}") !important;
     left: @menuMinWidth !important;
   }
+  .header__wrap--tab--collapsed {
+    width: calc(~"100% - @{menuMinWidth} - @{menuTabWidth}") !important;
+    left: @menuMinWidth + @menuTabWidth !important;
+  }
 }
 .app__wrap--Classic {
   .header__wrap--fixed {

+ 10 - 0
src/pages/index/store/modules/app.ts

@@ -17,6 +17,7 @@ export interface AppState {
   userInfo: String
   greyMode: Boolean
   showBackTop: Boolean
+  showMenuTab: Boolean
 }
 
 @Module({ dynamic: true, namespaced: true, store, name: 'app' })
@@ -36,6 +37,7 @@ class App extends VuexModule implements AppState {
   public userInfo = 'userInfo' // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
   public greyMode = false // 是否开始灰色模式,用于特殊悼念日
   public showBackTop = true // 是否显示回到顶部
+  public showMenuTab = false // 是否固定一级菜单
 
   @Mutation
   private SET_COLLAPSED(collapsed: boolean): void {
@@ -93,6 +95,10 @@ class App extends VuexModule implements AppState {
   private SET_SHOWBACKTOP(showBackTop: boolean): void {
     this.showBackTop = showBackTop
   }
+  @Mutation
+  private SET_SHOWMENUTAB(showMenuTab: boolean): void {
+    this.showMenuTab = showMenuTab
+  }
 
   @Action
   public SetCollapsed(collapsed: boolean): void {
@@ -150,6 +156,10 @@ class App extends VuexModule implements AppState {
   public SetShowBackTop(showBackTop: boolean): void {
     this.SET_SHOWBACKTOP(showBackTop)
   }
+  @Action
+  public SetShowMenuTab(showMenuTab: boolean): void {
+    this.SET_SHOWMENUTAB(showMenuTab)
+  }
 }
 
 export const appStore = getModule<App>(App)

+ 20 - 0
src/pages/index/store/modules/permission.ts

@@ -17,6 +17,8 @@ export interface PermissionState {
   routers: AppRouteRecordRaw[]
   addRouters: AppRouteRecordRaw[]
   isAddRouters: boolean
+  activeTab: string
+  menuTabRouters: AppRouteRecordRaw[]
 }
 
 @Module({ dynamic: true, namespaced: true, store, name: 'permission' })
@@ -24,6 +26,8 @@ class Permission extends VuexModule implements PermissionState {
   public routers = [] as any[]
   public addRouters = [] as any[]
   public isAddRouters = false
+  public menuTabRouters = [] as any[]
+  public activeTab = ''
 
   @Mutation
   private SET_ROUTERS(routers: AppRouteRecordRaw[]): void {
@@ -44,6 +48,14 @@ class Permission extends VuexModule implements PermissionState {
   private SET_ISADDROUTERS(state: boolean): void {
     this.isAddRouters = state
   }
+  @Mutation
+  private SET_MENUTABROUTERS(routers: AppRouteRecordRaw[]): void {
+    this.menuTabRouters = routers
+  }
+  @Mutation
+  private SET_ACTIVETAB(activeTab: string): void {
+    this.activeTab = activeTab
+  }
 
   @Action
   public GenerateRoutes(): Promise<unknown> {
@@ -66,6 +78,14 @@ class Permission extends VuexModule implements PermissionState {
   public SetIsAddRouters(state: boolean): void {
     this.SET_ISADDROUTERS(state)
   }
+  @Action
+  public SetMenuTabRouters(routers: AppRouteRecordRaw[]): void {
+    this.SET_MENUTABROUTERS(routers)
+  }
+  @Action
+  public SetAcitveTab(activeTab: string): void {
+    this.SET_ACTIVETAB(activeTab)
+  }
 }
 
 // 路由过滤,主要用于权限控制

+ 15 - 0
src/styles/variables.less

@@ -25,6 +25,21 @@
 @topSMenuHover: #2d8cf0;
 @topSMenuActiveText: #2d8cf0;
 
+// meunTab
+@menuTabWidth: 90px;
+@menuTabItemHeight: 70px;
+@menuTabBg: #fff;
+@menuTabText: black;
+@menuTabActiveBg: #2d8cf0;
+@menuTabActiveText: #fff;
+
+// menuTopTab
+@menuTopTabWidth: 120px;
+@menuTopTabBg: #fff;
+@menuTopTabText: black;
+@menuTopTabActiveBg: #2d8cf0;
+@menuTopTabActiveText: #fff;
+
 // navbar
 @navbarHeight: 40px;
 

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

@@ -7,6 +7,7 @@ export interface IScssVariables {
   subMenuHover: string
   menuWidth: string
   menuMinWidth: string
+  
 }
 
 export const variables: IScssVariables