聊聊element-ui 側邊欄的router問題
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,註意如果存在子菜單,需要填入子菜單 IDunique-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 中的配置項是否需要展示 logovariables
:從 @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 的 classitem
: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 組件
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- vue-element-admin關閉eslint的校驗方式
- vue.js使用Element-ui中實現導航菜單
- Vue實現路由嵌套的方法實例
- vue router-view的嵌套顯示實現
- vue遞歸組件實現elementUI多級菜單