vue点击按钮收缩菜单

问题描述

VUE菜单有一个BUG,当我们点击其它按钮或者首页的时候,已经展示的一级菜单是不会自动收缩的。这个问题也导致很多开发者把一级菜单都换成了二级菜单。

错误展示

错误的效果请看下图。

解决方法

1、寻找菜单文件

因为我使用的是ruoyi的前端框架,所以菜单文件的路径是src/layout/components/Sidebar/index.vue文件,如果大家使用的是其他的框架或者自己写的去全局搜索关键字【el-menu】就能找到菜单页面。文件路径如下图

2、添加以下代码

html 复制代码
// el-menu菜单中添加ref和open事件
<el-menu ref="menu"  @open="handleOpen"></el-menu>
data() {
        return {
            // 记录用户上次点击的菜单索引
            keyIndex:0,
        };
    },
    watch: {
        $route () {
            // 监控用户点击的菜单,如果是首页或者个人详情页面都要把上次打开的页面收缩起来。
            if (this.$route.path === '/index' ||this.$route.path ===  "/user/profile") {
                this.$refs.menu.close(this.keyIndex);
            }
        }
    },
    methods: {
        handleOpen (key) {
            this.keyIndex = key;
        }
    },

3、完整的代码

html 复制代码
<template>
    <div :class="{'has-logo':showLogo}" :style="{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
        <logo v-if="showLogo" :collapse="isCollapse" />
        <el-scrollbar :class="settings.sideTheme" wrap-class="scrollbar-wrapper">
            <el-menu
                ref="menu"
                @open="handleOpen"
                :default-active="activeMenu"
                :collapse="isCollapse"
                :background-color="settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
                :text-color="settings.sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
                :unique-opened="true"
                :active-text-color="settings.theme"
                :collapse-transition="false"
                mode="vertical"
            >
                <sidebar-item
                    v-for="(route, index) in sidebarRouters"
                    :key="route.path  + index"
                    :item="route"
                    :base-path="route.path"
                />
            </el-menu>
        </el-scrollbar>
    </div>
</template>

<script>
import { mapGetters, mapState } from "vuex";
import Logo from "./Logo";
import SidebarItem from "./SidebarItem";
import variables from "@/assets/styles/variables.scss";

export default {
    components: { SidebarItem, Logo },
    data() {
        return {
            keyIndex:0,
        };
    },
    watch: {
        $route () {
            if (this.$route.path === '/index' ||this.$route.path ===  "/user/profile") {
                this.$refs.menu.close(this.keyIndex);
            }
        }
    },
    methods: {
        handleOpen (key) {
            this.keyIndex = key;
        }
    },
    computed: {
        ...mapState(["settings"]),
        ...mapGetters(["sidebarRouters", "sidebar"]),
        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;
        },
        showLogo() {
            return this.$store.state.settings.sidebarLogo;
        },
        variables() {
            return variables;
        },
        isCollapse() {
            return !this.sidebar.opened;
        }
    }
};
</script>

4、修改后的效果

5、到此功能完成。

-----华丽的分割线,以下是凑字数,大家不用花时间看,快去改代码-----

-----华丽的分割线,以下是凑字数,大家不用花时间看,快去改代码-----

-----华丽的分割线,以下是凑字数,大家不用花时间看,快去改代码-----

html 复制代码
<template>
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="$t(onlyOneChild.meta.title)" />
        </el-menu-item>
      </app-link>
    </template>

    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template slot="title">
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-submenu>
  </div>
</template>

<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'

export default {
  name: 'SidebarItem',
  components: { Item, AppLink },
  mixins: [FixiOSBug],
  props: {
    // route object
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ''
    }
  },
  data() {
    this.onlyOneChild = null
    return {}
  },
  methods: {
    hasOneShowingChild(children = [], parent) {
      if (!children) {
        children = [];
      }
      const showingChildren = children.filter(item => {
        if (item.hidden) {
          return false
        } else {
          // Temp set(will be used if only has one showing child)
          this.onlyOneChild = 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) {
        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
        return true
      }

      return false
    },
    resolvePath(routePath, routeQuery) {
      if (isExternal(routePath)) {
        return routePath
      }
      if (isExternal(this.basePath)) {
        return this.basePath
      }
      if (routeQuery) {
        let query = JSON.parse(routeQuery);
        return { path: path.resolve(this.basePath, routePath), query: query }
      }
      return path.resolve(this.basePath, routePath)
    }
  }
}
</script>
相关推荐
碎碎念_4928 小时前
SpringBoot + Vue 前后端分离从 0 到 1 完整环境配置流程
vue.js·spring boot·后端
shuaijie05189 小时前
强制修改调用接口的api地址。
javascript·vue.js·ecmascript
katiua12 小时前
Vue-i18n TypeScript 补全提示
vue.js
Fuzio13 小时前
用 Spring Boot + Vue + Fuzio 构建现代 Java 桌面应用
vue.js·spring boot
kisshyshy13 小时前
从0到1彻底理解流式输出:先读懂Vue的“乐高积木”,再拧开LLM的“数据水龙头”
javascript·vue.js·人工智能
饮茶三千13 小时前
电子保函模板编辑器实战二:HTML→FTL 编译转换的设计与实现
前端·vue.js·设计模式
英勇无比的消炎药13 小时前
表单筛选利器:TinyVue BaseSelect 基础选择器实战
vue.js
先吃饱再说14 小时前
从零理解 Vue 3:组件化、数据绑定与响应式完全指南
vue.js·前端框架
gis开发之家15 小时前
《Vue3 从入门到大神29篇》单元测试与 E2E 测试 —— 保障 Vue3 项目的质量
前端·javascript·vue.js·单元测试·前端框架·vue3
forever_world16 小时前
# 💧 从前端视角,彻底搞懂 AI 流式输出 —— 从"接根水管"到 Vue 3 实战
前端·vue.js