Bläddra i källkod

菜单导航设置支持纯顶部

RuoYi 6 månader sedan
förälder
incheckning
49f62e565a

+ 12 - 0
ruoyi-ui/src/assets/styles/ruoyi.scss

@@ -130,6 +130,18 @@
   border-radius: 4px;
 }
 
+/* el menu */
+.el-menu-item,
+.el-sub-menu {
+  .svg-icon + span {
+    margin-left: 5px;
+  }
+}
+
+.el-menu--horizontal .el-menu--popup {
+  min-width: 120px !important;
+}
+
 @media (max-width: 768px) {
   .pagination-container .el-pagination > .el-pagination__jump {
     display: none !important;

+ 1 - 1
ruoyi-ui/src/assets/styles/sidebar.scss

@@ -61,7 +61,7 @@
     }
 
     .svg-icon {
-      margin-right: 16px;
+      margin-right: 10px !important;
     }
 
     .el-menu {

+ 0 - 1
ruoyi-ui/src/components/Breadcrumb/index.vue

@@ -94,7 +94,6 @@ export default {
   display: inline-block;
   font-size: 14px;
   line-height: 50px;
-  margin-left: 8px;
   .no-redirect {
     color: #97a8be;
     cursor: text;

+ 3 - 3
ruoyi-ui/src/components/TopNav/index.vue

@@ -162,7 +162,7 @@ export default {
         this.$store.dispatch('app/toggleSideBarHide', true)
       }
     }
-  },
+  }
 }
 </script>
 
@@ -171,7 +171,7 @@ export default {
   float: left;
   height: 50px !important;
   line-height: 50px !important;
-  color: #999093 !important;
+  color: #303133 !important;
   padding: 0 5px !important;
   margin: 0 10px !important;
 }
@@ -186,7 +186,7 @@ export default {
   float: left;
   height: 50px !important;
   line-height: 50px !important;
-  color: #999093 !important;
+  color: #303133 !important;
   padding: 0 5px !important;
   margin: 0 10px !important;
 }

+ 45 - 9
ruoyi-ui/src/layout/components/Navbar.vue

@@ -1,10 +1,13 @@
 <template>
-  <div class="navbar">
+  <div class="navbar" :class="'nav' + navType">
     <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
 
-    <breadcrumb v-if="!topNav" id="breadcrumb-container" class="breadcrumb-container" />
-    <top-nav v-if="topNav" id="topmenu-container" class="topmenu-container" />
-
+    <breadcrumb v-if="navType == 1" id="breadcrumb-container" class="breadcrumb-container" />
+    <top-nav v-if="navType == 2" id="topmenu-container" class="topmenu-container" />
+    <template v-if="navType == 3">
+      <logo v-show="showLogo" :collapse="false"></logo>
+      <top-bar id="topbar-container" class="topbar-container" />
+    </template>
     <div class="right-menu">
       <template v-if="device!=='mobile'">
         <search id="header-search" class="right-menu-item" />
@@ -50,6 +53,8 @@
 import { mapGetters } from 'vuex'
 import Breadcrumb from '@/components/Breadcrumb'
 import TopNav from '@/components/TopNav'
+import TopBar from './TopBar'
+import Logo from './Sidebar/Logo'
 import Hamburger from '@/components/Hamburger'
 import Screenfull from '@/components/Screenfull'
 import SizeSelect from '@/components/SizeSelect'
@@ -61,7 +66,9 @@ export default {
   emits: ['setLayout'],
   components: {
     Breadcrumb,
+    Logo,
     TopNav,
+    TopBar,
     Hamburger,
     Screenfull,
     SizeSelect,
@@ -81,9 +88,14 @@ export default {
         return this.$store.state.settings.showSettings
       }
     },
-    topNav: {
+    navType: {
       get() {
-        return this.$store.state.settings.topNav
+        return this.$store.state.settings.navType
+      }
+    },
+    showLogo: {
+      get() {
+        return this.$store.state.settings.sidebarLogo
       }
     }
   },
@@ -110,20 +122,33 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.navbar.nav3 {
+  .hamburger-container {
+    display: none !important;
+  }
+}
+
 .navbar {
   height: 50px;
   overflow: hidden;
   position: relative;
   background: #fff;
   box-shadow: 0 1px 4px rgba(0,21,41,.08);
+  display: flex;
+  align-items: center;
+  // padding: 0 8px;
+  box-sizing: border-box;
 
   .hamburger-container {
     line-height: 46px;
     height: 100%;
-    float: left;
     cursor: pointer;
     transition: background .3s;
     -webkit-tap-highlight-color:transparent;
+    display: flex;
+    align-items: center;
+    flex-shrink: 0;
+    margin-right: 8px;
 
     &:hover {
       background: rgba(0, 0, 0, .025)
@@ -131,7 +156,7 @@ export default {
   }
 
   .breadcrumb-container {
-    float: left;
+    flex-shrink: 0;
   }
 
   .topmenu-container {
@@ -139,15 +164,26 @@ export default {
     left: 50px;
   }
 
+  .topbar-container {
+    flex: 1;
+    min-width: 0;
+    display: flex;
+    align-items: center;
+    overflow: hidden;
+    margin-left: 8px;
+  }
+
   .errLog-container {
     display: inline-block;
     vertical-align: top;
   }
 
   .right-menu {
-    float: right;
     height: 100%;
     line-height: 50px;
+    display: flex;
+    align-items: center;
+    margin-left: auto;
 
     &:focus {
       outline: none;

+ 164 - 73
ruoyi-ui/src/layout/components/Settings/index.vue

@@ -4,6 +4,27 @@
       <div>
         <div class="setting-drawer-content">
           <div class="setting-drawer-title">
+            <h3 class="drawer-title">菜单导航设置</h3>
+          </div>
+          <div class="nav-wrap">
+            <el-tooltip content="左侧菜单" placement="bottom">
+              <div class="item left" @click="handleNavType(1)" :style="{'--theme': theme}" :class="{ activeItem: navType == 1 }">
+                <b></b><b></b>
+              </div>
+            </el-tooltip>
+
+            <el-tooltip content="混合菜单" placement="bottom">
+              <div class="item mix" @click="handleNavType(2)" :style="{'--theme': theme}" :class="{ activeItem: navType == 2 }">
+                <b></b><b></b>
+              </div>
+            </el-tooltip>
+            <el-tooltip content="顶部菜单" placement="bottom">
+              <div class="item top" @click="handleNavType(3)" :style="{'--theme': theme}" :class="{ activeItem: navType == 3 }">
+                <b></b><b></b>
+              </div>
+            </el-tooltip>
+          </div>
+          <div class="setting-drawer-title">
             <h3 class="drawer-title">主题风格设置</h3>
           </div>
           <div class="setting-drawer-block-checbox">
@@ -40,11 +61,6 @@
         <h3 class="drawer-title">系统布局配置</h3>
 
         <div class="drawer-item">
-          <span>开启 TopNav</span>
-          <el-switch v-model="topNav" class="drawer-switch" />
-        </div>
-
-        <div class="drawer-item">
           <span>开启 Tags-Views</span>
           <el-switch v-model="tagsView" class="drawer-switch" />
         </div>
@@ -93,6 +109,7 @@ export default {
     return {
       theme: this.$store.state.settings.theme,
       sideTheme: this.$store.state.settings.sideTheme,
+      navType: this.$store.state.settings.navType,
       showSettings: false
     }
   },
@@ -108,21 +125,6 @@ export default {
         })
       }
     },
-    topNav: {
-      get() {
-        return this.$store.state.settings.topNav
-      },
-      set(val) {
-        this.$store.dispatch('settings/changeSetting', {
-          key: 'topNav',
-          value: val
-        })
-        if (!val) {
-          this.$store.dispatch('app/toggleSideBarHide', false)
-          this.$store.commit("SET_SIDEBAR_ROUTERS", this.$store.state.permission.defaultRoutes)
-        }
-      }
-    },
     tagsView: {
       get() {
         return this.$store.state.settings.tagsView
@@ -180,6 +182,25 @@ export default {
       }
     }
   },
+  watch: {
+    navType: {
+      handler(val) {
+        if (val == 1) {
+          this.$store.dispatch("app/toggleSideBarHide", false)
+        }
+        if (val == 2) {
+        }
+        if (val == 3) {
+          this.$store.dispatch("app/toggleSideBarHide", true)
+        }
+        if ([1, 3].includes(val)) {
+          this.$store.commit("SET_SIDEBAR_ROUTERS",this.$store.state.permission.defaultRoutes)
+        }
+      },
+      immediate: true,
+      deep: true
+    }
+  },
   methods: {
     themeChange(val) {
       this.$store.dispatch('settings/changeSetting', {
@@ -195,6 +216,13 @@ export default {
       })
       this.sideTheme = val
     },
+    handleNavType(val) {
+      this.$store.dispatch('settings/changeSetting', {
+        key: 'navType',
+        value: val
+      })
+      this.navType = val
+    },
     openSetting() {
       this.showSettings = true
     },
@@ -206,7 +234,7 @@ export default {
       this.$cache.local.set(
         "layout-setting",
         `{
-            "topNav":${this.topNav},
+            "navType":${this.navType},
             "tagsView":${this.tagsView},
             "tagsIcon":${this.tagsIcon},
             "fixedHeader":${this.fixedHeader},
@@ -229,70 +257,133 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-  .setting-drawer-content {
-    .setting-drawer-title {
-      margin-bottom: 12px;
-      color: rgba(0, 0, 0, .85);
-      font-size: 14px;
-      line-height: 22px;
-      font-weight: bold;
-    }
+.setting-drawer-content {
+  .setting-drawer-title {
+    margin-bottom: 12px;
+    color: rgba(0, 0, 0, .85);
+    font-size: 14px;
+    line-height: 22px;
+    font-weight: bold;
+  }
 
-    .setting-drawer-block-checbox {
-      display: flex;
-      justify-content: flex-start;
-      align-items: center;
-      margin-top: 10px;
-      margin-bottom: 20px;
+  .setting-drawer-block-checbox {
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    margin-top: 10px;
+    margin-bottom: 20px;
 
-      .setting-drawer-block-checbox-item {
-        position: relative;
-        margin-right: 16px;
-        border-radius: 2px;
-        cursor: pointer;
+    .setting-drawer-block-checbox-item {
+      position: relative;
+      margin-right: 16px;
+      border-radius: 2px;
+      cursor: pointer;
 
-        img {
-          width: 48px;
-          height: 48px;
-        }
+      img {
+        width: 48px;
+        height: 48px;
+      }
 
-        .setting-drawer-block-checbox-selectIcon {
-          position: absolute;
-          top: 0;
-          right: 0;
-          width: 100%;
-          height: 100%;
-          padding-top: 15px;
-          padding-left: 24px;
-          color: #1890ff;
-          font-weight: 700;
-          font-size: 14px;
-        }
+      .setting-drawer-block-checbox-selectIcon {
+        position: absolute;
+        top: 0;
+        right: 0;
+        width: 100%;
+        height: 100%;
+        padding-top: 15px;
+        padding-left: 24px;
+        color: #1890ff;
+        font-weight: 700;
+        font-size: 14px;
       }
     }
   }
+}
+
+.drawer-container {
+  padding: 20px;
+  font-size: 14px;
+  line-height: 1.5;
+  word-wrap: break-word;
 
-  .drawer-container {
-    padding: 20px;
+  .drawer-title {
+    margin-bottom: 12px;
+    color: rgba(0, 0, 0, .85);
     font-size: 14px;
-    line-height: 1.5;
-    word-wrap: break-word;
+    line-height: 22px;
+  }
 
-    .drawer-title {
-      margin-bottom: 12px;
-      color: rgba(0, 0, 0, .85);
-      font-size: 14px;
-      line-height: 22px;
-    }
+  .drawer-item {
+    color: rgba(0, 0, 0, .65);
+    font-size: 14px;
+    padding: 12px 0;
+  }
 
-    .drawer-item {
-      color: rgba(0, 0, 0, .65);
-      font-size: 14px;
-      padding: 12px 0;
-    }
+  .drawer-switch {
+    float: right
+  }
+}
+
+// 导航模式
+.nav-wrap {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  margin-top: 10px;
+  margin-bottom: 20px;
+
+  .activeItem {
+    border: 2px solid #{'var(--theme)'} !important;
+  }
+
+  .item {
+    position: relative;
+    margin-right: 16px;
+    cursor: pointer;
+    width: 56px;
+    height: 48px;
+    border-radius: 4px;
+    background: #f0f2f5;
+    border: 2px solid transparent;
+  }
 
-    .drawer-switch {
-      float: right
+  .left {
+    b:first-child {
+      display: block;
+      height: 30%;
+      background: #fff;
+    }
+    b:last-child {
+      width: 30%;
+      background: #1b2a47;
+      position: absolute;
+      height: 100%;
+      top: 0;
+      border-radius: 4px 0 0 4px;
+    }
+  }
+  .mix {
+    b:first-child {
+      border-radius: 4px 4px 0 0;
+      display: block;
+      height: 30%;
+      background: #1b2a47;
+    }
+    b:last-child {
+      width: 30%;
+      background: #1b2a47;
+      position: absolute;
+      height: 70%;
+      border-radius: 0 0 0 4px;
     }
   }
+  .top {
+    b:first-child {
+      display: block;
+      height: 30%;
+      background: #1b2a47;
+      border-radius: 4px 4px 0 0;
+    }
+  }
+}
 </style>

+ 6 - 4
ruoyi-ui/src/layout/components/Sidebar/Logo.vue

@@ -1,13 +1,13 @@
 <template>
-  <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
+  <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' && navType !== 3 ? variables.menuBackground : variables.menuLightBackground }">
     <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" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
+        <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' && navType !== 3 ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ 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" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
+        <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' && navType !== 3 ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
       </router-link>
     </transition>
   </div>
@@ -31,6 +31,9 @@ export default {
     },
     sideTheme() {
       return this.$store.state.settings.sideTheme
+    },
+    navType() {
+      return this.$store.state.settings.navType
     }
   },
   data() {
@@ -54,7 +57,6 @@ export default {
 
 .sidebar-logo-container {
   position: relative;
-  width: 100%;
   height: 50px;
   line-height: 50px;
   background: #2b2f3a;

+ 108 - 0
ruoyi-ui/src/layout/components/TopBar/index.vue

@@ -0,0 +1,108 @@
+<template>
+  <el-menu class="topbar-menu" :default-active="activeMenu" :active-text-color="theme" mode="horizontal">
+    <sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" />
+
+    <el-submenu index="more" class="el-submenu__hide-arrow" v-if="moreRoutes.length > 0">
+      <template slot="title">更多菜单</template>
+      <sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" />
+    </el-submenu>
+  </el-menu>
+</template>
+
+<script>
+import SidebarItem from '../Sidebar/SidebarItem'
+
+export default {
+  components: { SidebarItem },
+  data() {
+    return {
+      // 顶部栏初始数
+      visibleNumber: 5
+    }
+  },
+  computed: {
+    theme() {
+      return this.$store.state.settings.theme
+    },
+    topMenus() {
+      return this.$store.state.permission.sidebarRouters.filter((f) => !f.hidden).slice(0, this.visibleNumber)
+    },
+    moreRoutes() {
+      const sidebarRouters = this.$store.state.permission.sidebarRouters;
+      return sidebarRouters.filter((f) => !f.hidden).slice(this.visibleNumber, sidebarRouters.length - this.visibleNumber)
+    },
+    // 默认激活的菜单
+    activeMenu() {
+      const { meta, path } = this.$route
+      if (meta.activeMenu) {
+        return meta.activeMenu
+      }
+      return path
+    },
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.setVisibleNumber)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.setVisibleNumber)
+  },
+  mounted() {
+    this.setVisibleNumber()
+  },
+  methods: {
+    // 根据宽度计算设置显示栏数
+    setVisibleNumber() {
+      const width = document.body.getBoundingClientRect().width / 3
+      this.visibleNumber = parseInt(width / 85)
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.topbar-menu.el-menu--horizontal > .el-menu-item {
+  float: left;
+  height: 50px !important;
+  line-height: 50px !important;
+  color: #303133 !important;
+  padding: 0 5px !important;
+  margin: 0 10px !important;
+}
+
+.el-menu-item.is-active .svg-icon + span, .el-submenu.is-active .svg-icon + span{
+  color: v-bind(theme);
+}
+
+.el-menu--horizontal .el-menu .el-menu-item, .el-menu--horizontal .el-menu .el-submenu__title {
+  color: #303133 !important;
+}
+
+/* submenu item */
+.topbar-menu.el-menu--horizontal > .el-submenu .el-submenu__title {
+  float: left;
+  height: 50px !important;
+  line-height: 50px !important;
+  color: #303133 !important;
+  padding: 0 5px !important;
+  margin: 0 10px !important;
+}
+
+/* 图标右间距 */
+.topbar-menu .svg-icon {
+  margin-right: 4px;
+}
+
+/* topbar more arrow */ 
+.topbar-menu .el-submenu .el-submenu__icon-arrow {
+  position: static;
+  vertical-align: middle;
+  margin-left: 8px;
+  margin-top: 0px;
+}
+
+/* menu__title el-menu-item */
+.topbar-menu.el-menu--horizontal .el-submenu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
+  height: 55px;
+  padding: 0 15px;
+}
+</style>

+ 2 - 2
ruoyi-ui/src/settings.js

@@ -15,9 +15,9 @@ module.exports = {
   showSettings: true,
 
   /**
-   * 是否显示顶部导航
+   * 菜单导航模式 1、纯左侧 2、混合(左侧+顶部) 3、纯顶部
    */
-  topNav: false,
+  navType: 1,
 
   /**
    * 是否显示 tagsView

+ 2 - 2
ruoyi-ui/src/store/modules/settings.js

@@ -1,7 +1,7 @@
 import defaultSettings from '@/settings'
 import { useDynamicTitle } from '@/utils/dynamicTitle'
 
-const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
+const { sideTheme, showSettings, navType, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
 
 const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
 const state = {
@@ -9,7 +9,7 @@ const state = {
   theme: storageSetting.theme || '#409EFF',
   sideTheme: storageSetting.sideTheme || sideTheme,
   showSettings: showSettings,
-  topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
+  navType: storageSetting.navType === undefined ? navType : storageSetting.navType,
   tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
   tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
   fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,