RuoYi-Vue将侧边栏菜单改为顶部导航菜单

3,127 阅读3分钟

前言:若依框架中前端菜单默认是在左侧展示,但是有些需求需要将菜单放在顶部。虽然框架自带的布局设置中有【开启 TopNav】功能,但该设置项也仅仅是将一级菜单置于顶部,其余菜单仍是在左侧展示,这可能并不是我们想要的效果。

示例项目已上传gitee,地址:gitee.com/SuperLucife…

预期效果

image.png

image.png

实现步骤

1.将src/layout/components/Sidebar目录下的指定文件复制到src/components/TopNav目录下

image.png

image.png

2.修改src/components/TopNav/index.vue(建议直接替换)

<template>
  <el-menu
    id="menu"
    :default-active="activeMenu"
    :unique-opened="true"
    :collapse-transition="false"
    mode="horizontal"
    menu-trigger="hover"
  >
    <sidebar-item
      v-for="(route, index) in visibleRouters"
      :key="route.path + index"
      :item="route"
      :base-path="route.path"
    />
    <!-- 顶部菜单超出数量折叠 -->
    <el-submenu v-if="moreRouters.length" index="more">
      <template slot="title">
        <svg-icon icon-class="table" />
        <span>更多菜单</span>
      </template>
      <sidebar-item
        class="nest-menu"
        v-for="(route, index) in moreRouters"
        :key="route.path + index"
        :item="route"
        :base-path="route.path"
      />
    </el-submenu>
  </el-menu>
</template>

<script>
import { mapGetters } from "vuex";
import { deepClone } from "@/utils/index";
import SidebarItem from "./SidebarItem.vue";

export default {
  props: {
    width: {
      type: Number,
      default: 0,
    },
  },
  components: { SidebarItem },
  data() {
    return {
      // 顶部栏初始数
      visibleNumber: 5,
      menuKey: new Date().getTime(),
    };
  },
  computed: {
    ...mapGetters(["sidebarRouters", "moduleList", "moduleCode"]),
    theme() {
      return this.$store.state.settings.theme;
    },
    // 所有的路由信息
    routers() {
      // 过滤掉隐藏的路由
      return this.sidebarRouters.filter((item) => !item.hidden);
    },
    // 可视菜单路由
    visibleRouters() {
      const length = this.routers.length;
      if (length <= this.visibleNumber) {
        return deepClone(this.routers);
      }
      return this.routers.slice(0, this.visibleNumber - 1);
    },
    // 更多菜单路由
    moreRouters() {
      const length = this.routers.length;
      if (length <= this.visibleNumber) {
        return [];
      }
      return this.routers.slice(this.visibleNumber - 1, length);
    },
    // 默认激活的菜单
    activeMenu() {
      const route = this.$route;
      const { meta, path } = route;
      // if set path, the sidebar will highlight the path you set
      if (meta.activeMenu) {
        return meta.activeMenu;
      }
      return path;
    },
  },
  watch: {
    width: {
      handler(newVal) {
        this.setVisibleNumber(newVal);
        // 更新菜单组件key,避免当前菜单被折叠后,再次出现时未高亮显示
        this.menuKey = new Date().getTime();
      },
      immediate: true,
    },
  },
  mounted() {},
  methods: {
    // 根据宽度计算设置显示栏数
    setVisibleNumber(width) {
      // 112是取的菜单项最大宽度,此处还可进行优化
      this.visibleNumber = parseInt(width / 112);
    },
  },
};
</script>

<style lang="scss">
.el-menu-item {
  padding: 0 10px;
  color: #303133 !important;

  &:hover {
    background-color: #e8f4ff !important;
  }

  &.is-active {
    color: #1890ff !important;
  }

  .svg-icon {
    margin-left: 0 !important;
    margin-right: 5px;
  }
}

.el-submenu {
  &.is-active {
    .el-submenu__title {
      color: #1890ff !important;
    }
  }

  .el-submenu__title {
    display: flex;
    align-items: center;
    padding: 0 10px;
    color: #303133 !important;

    &:hover {
      background-color: #e8f4ff !important;
    }

    .svg-icon {
      margin-left: 0 !important;
      margin-right: 5px;
    }

    .el-submenu__icon-arrow {
      position: initial;
      margin-left: 5px;
      margin-top: 1px;
    }
  }
}

.el-menu--popup {
  .el-submenu__icon-arrow {
    margin-left: auto !important;
  }
}
</style>

3.安装元素尺寸监听插件element-resize-detector

npm install element-resize-detector@1.2.4

4.在src/directive/module目录下新建wResize.js文件,文件内容如下:

// 元素尺寸监听插件
import ElementResizeDetectorMaker from 'element-resize-detector'
import debounce from 'lodash/debounce'

/**
 * 元素宽度监听
 * 使用:v-wResize="onWidthResize"
 * onWidthResize({width})
 */
export default {
  bind(el, binding) {
    let width = 0
    const erd = ElementResizeDetectorMaker()
		// 防抖
    const debounceHandler = element =>
      debounce(() => {
        let offsetWidth = element.offsetWidth
        if (width !== offsetWidth) {
          width = offsetWidth
          binding.value({ width })
        }
      }, 100)()
    erd.listenTo(el, debounceHandler)
    el.__vueErd__ = erd
  },

  // 销毁时移除detector
  unbind(el) {
    el.__vueErd__.uninstall(el)
  },
}

image.png

5.注册v-wResize指令

image.png

import wResize from './module/wResize'

Vue.directive('wResize', wResize)

6.修改src/layout/components/navbar.vue

  • 修改模板内容 image.png
<div v-wResize="onMenuContResize" id="navbar-left" class="navbar__left">
  <top-nav
    v-if="menuContWidth"
    id="topmenu-container"
    class="topmenu-container"
    :width="menuContWidth"
  />
</div>
  • 添加变量menuContWidth
 data() {
    return {
      menuContWidth: 0,
    };
 },
  • 添加方法onMenuContResize
// 菜单容器宽度变化回调
onMenuContResize({ width }) {
  this.menuContWidth = width;
},
  • 修改样式(直接替换)
.navbar {
  height: 50px;
  overflow: hidden;
  position: relative;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  display: flex;

  &__left {
    flex: 1;
    overflow: hidden;
    margin-left: 10px;
    margin-right: 20px;
  }

  .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;
  }

  .topmenu-container {
    display: inline-flex;
    align-items: center;
    height: 50px;
    border: none;
  }

  .errLog-container {
    display: inline-block;
    vertical-align: top;
  }

  .right-menu {
    flex-shrink: 0;
    margin-left: auto;
    height: 100%;
    display: flex;
    align-items: center;

    &:focus {
      outline: none;
    }

    .right-menu-item {
      display: inline-flex;
      align-items: center;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;

      &.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;
        }
      }
    }
  }
}

7.修改src/layout/index.vue

  • 隐藏侧边栏 image.png

  • 修改固定header的样式 image.png

8.修改src/asssets/styles/sidebar.scss

image.png

image.png

9.修改src/layout/components/Settings/index.vue,隐藏布局设置中的【开启 TopNav】、【显示 Logo】

image.png