聊聊element-ui 侧边栏的router问题

目录
  • element-ui 侧边栏的router
  • element-ui中侧边栏的分析及实现
    • el-menu 和 el-submenu
    • sidebar 分析
    • 侧边栏实现总结

element-ui 侧边栏的router

所以就动态获取api里面的path值作为其router就行。注意带上上级路由。用字符串拼接的方式::index="'/home/'

对二级导航栏,这里的item是 v-for循环上级的item.children(一级导航栏)的项。

<el-menu-item :index="'/home/' + item.path"></el-menu-item>

element-ui中侧边栏的分析及实现

el-menu 和 el-submenu

1.如果需要实现一个侧边栏,会如何设计?

2.侧边栏的核心是将根据权限过滤后的 router 和 el-menu 组件进行映射,所以 el-menu 和 el-submenu 是理解 sidebar 的基础。

3.el-menu 表示菜单容器组件,如下所示:

  • default-active:当前激活菜单的 index,注意如果存在子菜单,需要填入子菜单 ID
  • unique-opened 是否只保持一个子菜单的展开
  • mode 模式,枚举值,horizontal / vertical 这两种
  • collapse 是否水平折叠收起菜单(仅在 mode 为 vertical 时可用)
  • collapse-transition 是否开启折叠动画
  • @select 点击菜单事件,菜单激活回调 index: 选中菜单项的 index, indexPath: 选中菜单项的 index path
  • @open: sub-menu 展开的回调
  • @close: sub-menu 收起的回调

4.el-submenu,子菜单容器,el-menu 表示整个菜单, el-submenu 表示一个具体菜单,只是该菜单还包括了子菜单。 el-submenu 可以通过定制 slot 的 title 来自定义菜单模式。el-submenu 容器内 default 的 slot 用来存放子菜单,可以包括三种子菜单组件,如下所示:

  • el-menu-item-group:菜单分组,为一组菜单添加一个标题,容器内需要存放 el-menu-item,支持通过 title 的 slot 来定制标题样式
  • el-submenu:支持循环嵌套 el-submenu,可以使得超过两级子组件得以实现
  • el-menu-item:子菜单组件

示例代码,如下所示:

<el-row class="tac">
  <el-col :span="12">
    <h5>默认颜色</h5>
    <el-menu
      default-active="2"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose">
      <el-submenu index="1">
        <template slot="title">
          <i class="el-icon-location"></i>
          <span>导航一</span>
        </template>
        <el-menu-item-group>
          <template slot="title">分组一</template>
          <el-menu-item index="1-1">选项1</el-menu-item>
          <el-menu-item index="1-2">选项2</el-menu-item>
        </el-menu-item-group>
        <el-menu-item-group title="分组2">
          <el-menu-item index="1-3">选项3</el-menu-item>
        </el-menu-item-group>
        <el-submenu index="1-4">
          <template slot="title">选项4</template>
          <el-menu-item index="1-4-1">选项1</el-menu-item>
        </el-submenu>
      </el-submenu>
      <el-menu-item index="2">
        <i class="el-icon-menu"></i>
        <span slot="title">导航二</span>
      </el-menu-item>
      <el-menu-item index="3" disabled>
        <i class="el-icon-document"></i>
        <span slot="title">导航三</span>
      </el-menu-item>
      <el-menu-item index="4">
        <i class="el-icon-setting"></i>
        <span slot="title">导航四</span>
      </el-menu-item>
    </el-menu>
  </el-col>
  <el-col :span="12">
    <h5>自定义颜色</h5>
    <el-menu
      default-active="2"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose"
      background-color="#545c64"
      text-color="#fff"
      active-text-color="#ffd04b">
      <el-submenu index="1">
        <template slot="title">
          <i class="el-icon-location"></i>
          <span>导航一</span>
        </template>
        <el-menu-item-group>
          <template slot="title">分组一</template>
          <el-menu-item index="1-1">选项1</el-menu-item>
          <el-menu-item index="1-2">选项2</el-menu-item>
        </el-menu-item-group>
        <el-menu-item-group title="分组2">
          <el-menu-item index="1-3">选项3</el-menu-item>
        </el-menu-item-group>
        <el-submenu index="1-4">
          <template slot="title">选项4</template>
          <el-menu-item index="1-4-1">选项1</el-menu-item>
        </el-submenu>
      </el-submenu>
      <el-menu-item index="2">
        <i class="el-icon-menu"></i>
        <span slot="title">导航二</span>
      </el-menu-item>
      <el-menu-item index="3" disabled>
        <i class="el-icon-document"></i>
        <span slot="title">导航三</span>
      </el-menu-item>
      <el-menu-item index="4">
        <i class="el-icon-setting"></i>
        <span slot="title">导航四</span>
      </el-menu-item>
    </el-menu>
  </el-col>
</el-row>
<script>
  export default {
    methods: {
      handleOpen(key, keyPath) {
        console.log(key, keyPath);
      },
      handleClose(key, keyPath) {
        console.log(key, keyPath);
      }
    }
  }
</script>

sidebar 分析

1.sidebar,如下所示:

  • activeMenu:通过 meta.activeMenu 属性,指定路由对应的高亮菜单,meta.activeMenu 需要提供一个合法的路由,否则不能生效。
  • isCollapse:NavBar 中点击按钮,会修改 Cookie 中的 sidebarStatus,从 vuex 取值时会将 sidebarStatus 转为 Boolean,并判断默认是否需要收缩左侧菜单栏
  • showLogo:判断 settings.js 中的配置项是否需要展示 logo
  • variables:从 @style/variables.css 中获取 scss 对象,从而获取样式

2.sidebar,代码实现如下:

<template>
  <div :class="{'has-logo':showLogo}">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="variables.menuBg"
        :text-color="variables.menuText"
        :unique-opened="false"
        :active-text-color="variables.menuActiveText"
        :collapse-transition="false"
        mode="vertical"
      >
        <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
      </el-menu>
    </el-scrollbar>
  </div>
</template>
<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
export default {
  components: { SidebarItem, Logo },
  computed: {
    ...mapGetters([
      'permission_routes',
      'sidebar'
    ]),
    activeMenu() {
      const route = this.$route
      const { meta, path } = route
      if (meta.activeMenu) {
        return meta.activeMenu
      }
      return path
    },
    showLogo() {
      return this.$store.state.settings.sidebarLogo
    },
    variables() {
      return variables
    },
    isCollapse() {
      return !this.sidebar.opened
    }
  }
}
</script>

3.sidebar 中通过 sidebar -item 实现子菜单,sidebar-item 的 props 是 item 为路由对象,basePath 是路由路径。sidebar-item 的展示逻辑,如下所示:

通过 item.hidden 控制菜单是否展示

通过 hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow 逻辑判断 template 菜单是否展示,template 代表单一菜单,如下所示:

  • hasOneShowingChild:判断是否只有一个需要展示的子路由
  • !onlyOneChild.children||onlyOneChild.noShowingChildren: 判断需要展示的子菜单,是否包含 children 属性,如果包含,则说明子菜单可能存在孙子菜单,此时需要再判断 noShowingChildren 属性
  • !item.alwaysShow:判断路由中是否存在 alwaysShow 属性,如何存在,则返回 false,不展示 template 菜单,也就是只要配置了 alwaysShow 属性就会直接进入 el-submenu 组件

4.对于 hasOneShowingChild 方法,children 是 router 对象的 children 属性,item 是 router 对象,代码如下所示:

hasOneShowingChild(children = [], parent) {
  const showingChildren = children.filter(item => {
    // 如果 children 中的路由包含 hidden 属性,则返回 false
    if (item.hidden) {
      return false
    } else {
      // 将子路由赋值给 onlyOneChild,用于只包含一个路由时展示
      this.onlyOneChild = item
      return true
    }
  })
  // 如果过滤后,只包含展示一个路由,则返回 true
  if (showingChildren.length === 1) {
    return true
  }
  // 如果没有子路由需要展示,则将 onlyOneChild 的 push 设置空路由,并添加 noShowingChildren
  if (showingChildren.length === 0) {
    this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
    return true
  }
  // 返回 false,表示不需要展示子路由,或者超过 需要展示的子路由
  return false
}

5.对于它们之间的关系,如下所示:

如果展示 template 组件,首先会展示 app-link 组件,然后是 el-menu-item,最里面嵌套的是 item 组件。item 组件需要 meta 中包含 title 和 icon 属性,否则将渲染内容为空的 vnode 对象。

如果 template 菜单不展示,则展示 el-submenu 菜单,el-submenu 逻辑中采用了嵌套组件的做法,将 sidebar-item 嵌套在 el-submenu 中

el-submenu 中的 sidebar-item 的区别,第一个是传入 is-nest 参数,第二个是传入 base-path 参数

6.sidebar-item,代码如下所示:

<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)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="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) {
      const showingChildren = children.filter(item => {
        // 如果 children 中的路由包含 hidden 属性,则返回 false
        if (item.hidden) {
          return false
        } else {
          // 将子路由赋值给 onlyOneChild,用于只包含一个路由时展示
          this.onlyOneChild = item
          return true
        }
      })
      // 如果过滤后,只包含展示一个路由,则返回 true
      if (showingChildren.length === 1) {
        return true
      }
      // 如果没有子路由需要展示,则将 onlyOneChild 的 push 设置空路由,并添加 noShowingChildren
      if (showingChildren.length === 0) {
        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
        return true
      }
      // 返回 false,表示不需要展示子路由,或者超过 需要展示的子路由
      return false
    },
    resolvePath(routePath) {
      if (isExternal(routePath)) {
        return routePath
      }
      if (isExternal(this.basePath)) {
        return this.basePath
      }
      return path.resolve(this.basePath, routePath)
    }
  }
}
</script>

7.app-link,是一个动态组件,通过 to 参数,如果包含 http 前缀则变成一个 a 标签,否则变成一个 router-link 组件。

8.app-link,代码如下所示:

<template>
  <component :is="type" v-bind="linkProps(to)">
    <slot />
  </component>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
  props: {
    to: {
      type: String,
      required: true
    }
  },
  computed: {
    isExternal() {
      return isExternal(this.to)
    },
    type() {
      if (this.isExternal) {
        return 'a'
      }
      return 'router-link'
    }
  },
  methods: {
    linkProps(to) {
      if (this.isExternal) {
        return {
          href: to,
          target: '_blank',
          rel: 'noopener'
        }
      }
      return {
        to: to
      }
    }
  }
}
</script>

9.item 组件,通过定义的 render 函数完成组件渲染。

10item,代码如下所示:

<script>
export default {
  name: 'MenuItem',
  functional: true,
  props: {
    icon: {
      type: String,
      default: ''
    },
    title: {
      type: String,
      default: ''
    }
  },
  render(h, context) {
    const { icon, title } = context.props
    const vnodes = []
    if (icon) {
      if (icon.includes('el-icon')) {
        vnodes.push(<i class={[icon, 'sub-el-icon']} />)
      } else {
        vnodes.push(<svg-icon icon-class={icon}/>)
      }
    }
    if (title) {
      vnodes.push(<span slot='title'>{(title)}</span>)
    }
    return vnodes
  }
}
</script>
<style scoped>
.sub-el-icon {
  color: currentColor;
  width: 1em;
  height: 1em;
}
</style>

侧边栏实现总结

1.sidebar 主要包括 el-menu 容器组件,el-menu 中遍历 vuex 中的 routes,生成 sidebar-item 组件。sidebar 主要配置如下所示:

  • activeMenu:根据当前路由的 meta.activeMenu 属性控制侧边栏中高亮菜单
  • isCollapse:根据 Cookie 的 sidebarStatus 控制侧边栏是否折叠
  • variables:通过 @style/variables.css 填充 el-menu 的基本样式

2.sidebar-item,分为两个部分,如下所示:

第一部分是当只需要展示一个 children 或者没有 children 时进行展示,展示的组件包括:

  • app-link:动态组件,path 为链接时,显示为 a 标签,path 为路由时,显示为 router-link 组件
  • el-menu-item:菜单项,当 sidebar-item 为非 nest 组件时,el-menu-item 会增加 submenu-title-noDropdown 的 class
  • item:el-menu-item 里的内容,主要是 icon 和 title,当 title 为空时,整个菜单项将不会展示

第二部分是当 children 超过两项时进行展示,展示的组件包括:

el-submenu:子菜单组件容器,用于嵌套子菜单组件

sidebar-item:el-submen 迭代嵌套了 sidebar-item 组件,在 sidebar-item 组件中的变化,设置了 is-nest 属性为 true,根据 child.path 生成了 base-path 属性传入 sidebar-item 组件

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • element-ui使用导航栏跳转路由的用法详解

    最近初学vue,试着做一个小项目熟悉语法与思想,其中使用elemen-ui的导航栏做路由跳转切换页面.下面记录一下学习过程 element-ui引入vue项目的用法参考element官网 首先复制官网的例子,在这基础上再修改成我们想要的样子. <el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSe

  • vue+elementUI组件递归实现可折叠动态渲染多级侧边栏导航

    早就实现了功能,但是发现点击的时候,选中的菜单项背景色会变白,周五时候仔细观察了一下,发现并不是调整样式的问题,而是选项没有被选中,于是好好研究了一下组件递归这块,总结记录一下心路历程 一.概念 递归:递归其实说白了,就是自己调用自己,样子就像是套娃一个套一个的,小时候玩过一个游戏汉诺塔就是利用的递归原理: 函数递归:函数利用函数名还调用自己 组件递归:所以组件递归利用的是vue组件中的name属性来实现的 二.需求 实现可折叠动态渲染多级侧边栏导航 三.分析 1.观察到侧边栏导航是一级一级的,

  • vue-router配合ElementUI实现导航的实例

    在每个项目中路由是不可或缺的,最近学习vue-router和ElementUI配合使用实现导航栏,在学习的过程中遇到一个问题:点击浏览器的刷新之后页面停留在原来的位置,但是导航却是默认第一个. 由于接触前端时间不长,对于路由的概念不是特别清楚,按照文档写了之后完全不知道怎么下手了,请教了同事,同事的解决办法是利用vuex管理,但是vuex这块还没有接触过,所以这个问题就一直搁置了,今天周末自己在家学习偶然直到了可以使用$route.path设置默认选中的导航,但是设置之后没有什么效果,刷新时页面

  • 聊聊element-ui 侧边栏的router问题

    目录 element-ui 侧边栏的router element-ui中侧边栏的分析及实现 el-menu 和 el-submenu sidebar 分析 侧边栏实现总结 element-ui 侧边栏的router 所以就动态获取api里面的path值作为其router就行.注意带上上级路由.用字符串拼接的方式::index="'/home/' 对二级导航栏,这里的item是 v-for循环上级的item.children(一级导航栏)的项. <el-menu-item :index=&q

  • vue+axios+element ui 实现全局loading加载示例

    实现全局loading加载 分析需求,我们只需要在请求发起的时候开始loading,响应结束的时候关闭loading,就这么简单 对不对? import axios from 'axios'; import { Message, Loading } from 'element-ui'; import Cookies from 'js-cookie'; import router from '@/router/index' let loading //定义loading变量 function st

  • Vue+Element UI实现下拉菜单的封装

    本文实例为大家分享了Vue+Element UI实现下拉菜单封装的具体代码,供大家参考,具体内容如下 1.效果图 先贴个效果图,菜单项没有做样式美化,图中显示的边框也是没有的(边框是外部容器的边框),其它的根据需要自己修改一下样式即可. 2.组件封装 组件的封装用到了CSS动画.定位.,以及Element UI提供的下拉菜单组件el-dropdown.代码如下, <template> <div class="all" @click="clickFire&qu

  • 关于element ui的菜单default-active默认选中的问题

    目录 element ui的菜单default-active默认选中 代码如下 问题如下 于是思考 最后发现 更新版本 最新说明 element菜单默认展开和选中 element ui的菜单default-active默认选中 代码如下 <el-menu router :default-active="currentMenu" active-text-color="#7BE4FF" :unique-opened="true"> //

  • 如何利用vue+element ui实现好看的登录界面

    目录 界面效果图 下面直接上代码: 附加背景图片 总结 闲暇之余使用vue+element ui制作了个登录界面 话不多说,先上 界面效果图 下面直接上代码: <template> <div class="loginbody"> <div class="logindata"> <div class="logintext"> <h2>Welcome</h2> </div&

  • Vue前端整合Element Ui的教程详解

    目录 Vue前端整合Element Ui 1.安装Element Ui组件库 2.测试Element Ui是否整合成功 Vue前端整合Element Ui 本节内容服务于SpringBoot + Vue 搭建 JavaWeb 增删改查项目. Element UI 是一套采用 Vue 2.0 作为基础框架实现的组件库,一套为开发者.设计师和产品经理准备的基于 Vue 2.0 的组件库,提供了配套设计资源,帮助网站快速成型. Element Ui组件官网 1.安装Element Ui组件库 WIN+

  • 关于element ui 表格中的常见特殊属性说明

    目录 1,表格内容太多用…表示 2,修改element ui自带的样式 3,修改奇数行背景色 4,给表头添加背景色及文字样式 5,表头文字竖向排列(文字带括号) 6,表头边框与文本边框对不齐情况 7,导航栏的侧边栏只展开一个下拉菜单 8,表格表头和内容居中显示 9,添加表格背景色 10,鼠标移入表格高亮显示当前行 element ui 表格的常见特殊属性 1,表格内容太多用...表示2,修改element ui自带的样式3,修改奇数行背景色4,给表头添加背景色及文字样式5,表头文字竖向排列(文字

  • element ui提交表单返回成功后自动清空表单的值的实现代码

    在实际开发中,新增弹窗的form表单中输入内容后,新增成功后应该把form表单清空,不然下次再进入新增弹窗时,会有上次新增完的内容 方法一:form表单项少的话可以选择手动删除: this.loginForm = { name:'', username:'', password:'', confirm: '', department: '', phone: '' } 方法二:以上的方法当然也是可以的,但是如果form表单有很多项的话,你需要写很多清空代码:elementui中的form提供res

  • 解决WebStorm 2022.3.x 无法识别 Element UI 2.15.11 新版本中的 el-xxx 标签问题(两种解决方案)

    目录 问题解读 解决(方案一) 解决(方案二) 问题解读 如题,其实2.15.11这个版本的Element UI新增了功能,改进WebStorm IDE和其他JetBrains IDE中的代码帮助.本义是想很好的支持IDE软件,代码提示更加方便,但作者发布时候少打包了一个文件,这就导致Webstorm不认识所有的el-xxx标签!!! 解决(方案一) 既然知道了这个版本有点小问题,那就简单粗暴,暂时降级到2.15.10版本,提示就有了.等过段时间官方修复了BUG,再改回最新版即可. 注意,由于我

  • 详解vue2.0的Element UI的表格table列时间戳格式化

    这两天学习了vue2.0的Element UI的表格table列时间戳格式化,所以,今天添加一点小笔记. 表格属性 <el-table :data="tableData" v-loading.body="loading" border @selection-change="selectionChange" style="width: 100%"> <el-table-column prop="cre

随机推荐