vue项目与iview3实现可折叠动态菜单。

  菜单实现一下效果:

  1. 动态获取项目路由生成动态三级菜单导航
  2. 可折叠展开
  3. 根据路由name默认打开子目录,选中当前项
  4. 自动过滤需要隐藏的路由(例:登陆)
  5. 在手机端首次进入自动收起全部的导航栏,pc端进入导航栏展开

  争议之处:当一级菜单项只有一个子元素时,只会显示一级菜单项不会展开下拉列表,设置子元素的显示(hidden)将无效。例如:主页

  demo效果如图显示,

  菜单使用iview3实现,菜单组件sider.vue的代码如下:

<template>
<Menu ref="asideMenu" width="100%" accordion :theme="theme1" :open-names="openItem" :active-name="activeName">
<!-- 动态菜单 -->
<div v-for="(item, index) in menuItems" :key="index" v-if="!app.isCollapsed && !item.meta.hidden">
<Submenu v-if="item.children && item.children.length>1 && !app.isCollapsed" :name="item.name">
<template slot="title">
<Icon :size="item.size" :type="item.type"/>
<span>{{item.text}}</span>
</template>
<div v-for="(subItem, i) in item.children" :key="index + i">
<Submenu v-if="subItem.children" :name="subItem.name">
<template slot="title">
<Icon :size="subItem.size" :type="subItem.type"/>
<span class="text-over">{{subItem.text}}</span>
</template>
<MenuItem class="menu-level-3" v-for="(threeItem, k) in subItem.children" :name="threeItem.name" :to="item.path+ '/' + subItem.path+ '/' + threeItem.path" :key="index + i + k">
<Icon :size="threeItem.size" :type="threeItem.type"/>
<span>{{threeItem.text}}</span>
</MenuItem>
</Submenu>
<MenuItem v-else :name="subItem.name" :to="item.path+ '/' + subItem.path">
<Icon :size="subItem.size" :type="subItem.type"/>
<span class="text-over">{{subItem.text}}</span>
</MenuItem>
</div>
</Submenu>
<MenuItem v-else :name="getName(item)" :to="item.path">
<Icon :size="item.size" :type="item.type" />
<span>{{item.text}}</span>
</MenuItem>
</div> <!-- 折叠菜单 -->
<div class="center-right" v-if="app.isCollapsed">
<div v-for="(item,index) in menuItems" :key = "index">
<Tooltip :content="item.text" placement="right" theme="light">
<Dropdown style="margin-left: 20px" trigger="click" placement="right-end" @on-click="toRoute">
<div class="collapsed-icon" @click="goRoute(item)"><Icon :type="item.type" size="18"></Icon></div>
<DropdownMenu slot="list" class="" v-if="item.children && item.children.length>1">
<div v-for="(secItem,i) in item.children" :key="i">
<Dropdown placement="right-start" v-if="secItem.children&& secItem.children.length>0">
<DropdownItem name=''>
{{secItem.text}}
<Icon type="ios-arrow-forward"></Icon>
</DropdownItem>
<DropdownMenu slot="list">
<DropdownItem v-for="(tt, t) in secItem.children" :key="t" :name="tt.name" >{{tt.text}}</DropdownItem>
</DropdownMenu>
</Dropdown>
<DropdownItem v-else :name="secItem.name">{{secItem.text}}</DropdownItem>
</div>
</DropdownMenu>
</Dropdown>
</Tooltip>
</div>
</div>
</Menu>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
data() {
return {
isShowAsideTitle: true,
theme1: 'dark',
openItem: [],
activeName: ''
}
},
computed: {
...mapState(['app', 'user']),
menuItems(state) {
return this.showMemuList(this.$router.options.routes)
},
// 获取的store中isCollapsed
getIsCollapsed() {
return this.$store.state.app.isCollapsed
}
},
watch: {
// 监听isCollapsed,当菜单展开时,默认当前打开,选中
getIsCollapsed() {
if (!this.app.isCollapsed) {
this.$nextTick(() => {
this.$refs.asideMenu.updateOpened()
this.$refs.asideMenu.updateActiveName()
})
}
},
// 监听路由,展开子目录,更新当前选择项
$route() {
this.openSideList()
this.activeName = this.$route.name
},
// 监听展开的子目录,更新
openItem() {
this.$nextTick(() => {
this.$refs.asideMenu.updateOpened()
})
}
},
methods: {
// 筛选需要显示的列表
showMemuList(list) {
let newArr = [];
list.map((item, index, arr) => {
// console.log(item, index, arr)
if (!item.meta.hidden) { // 显示
this.filterObj(item)
newArr.push(item)
}
})
return newArr
},
filterObj(obj) {
if (obj.children && obj.children.length > 1) {
obj.children = this.showMemuList(obj.children)
}
},
toRoute(name) {
if (name !== '') {
this.$router.push({ name: name })
} },
goRoute(item) {
if (item.children && item.children.length == 1) {
this.$router.push({ path: item.path })
} },
getName(item) {
return item.children[0].name
},
// 获取到展开的子目录 例:['/component']
openSideList() {
this.openItem = []
if (this.$route.matched.length > 2) {
this.openItem.push(this.$route.matched[0].name)
this.openItem.push(this.$route.matched[1].name)
} else
this.openItem.push(this.$route.matched[0].name)
}
},
mounted() {
this.openSideList()
this.activeName = this.$route.name
}
}
</script>
<style>
.center-right {
float: right;
}
.collapsed-icon {
width: 78px;
height: 78px;
text-align: center;
line-height: 78px;
}
.text-over {
display: inline-block;
width: 90px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
vertical-align: middle;
}
/* sider */
.layout {
border: 1px solid #d7dde4;
background: #f5f7f9;
position: relative;
border-radius: 4px;
overflow: hidden;
}
.layout-header-bar {
background: #fff;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
.layout-logo-left {
width: 90%;
height: 30px;
background: #5b6270;
border-radius: 3px;
margin: 15px auto;
}
.menu-icon {
transition: all 0.3s;
}
.rotate-icon {
transform: rotate(-90deg);
}
.ivu-menu {
white-space: nowrap;
}
.ivu-menu-item span {
display: inline-block;
overflow: hidden;
width: 69px;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
transition: width 0.2s ease 0.2s;
}
.ivu-menu-item i {
transform: translateX(0px);
transition: font-size 0.2s ease, transform 0.2s ease;
vertical-align: middle;
font-size: 16px;
}
.ivu-layout-sider-collapsed .ivu-menu-submenu-title span,
.ivu-layout-sider-collapsed .ivu-menu-item span {
display: inline-block;
width: 0px;
overflow: hidden;
transition: width 0.2s ease;
}
.ivu-layout-sider-collapsed
.ivu-menu-submenu-title
.ivu-menu-submenu-title-icon {
display: none;
}
.ivu-layout-sider-collapsed .ivu-menu-submenu-title i,
.ivu-layout-sider-collapsed .ivu-menu-item i {
transform: translateX(5px) translateY(-15px) scale(1.4);
transition: font-size 0.2s ease 0.2s, transform 0.2s ease 0.2s;
vertical-align: middle;
font-size: 22px;
}
</style>

  

  在sider.vue中,展开的菜单与折叠起来的菜单是分开写的,然后根据store中的状态判断是否展开收起。通过showMemuList()和filterObj()两个函数将不需要显示的路由过滤隐藏。

  在layout.vue整体布局文件中引用sider组件,内容如下:

<style lang="scss">
.height100 {
height: 100%;
}
.main-bg {
width: 100%;
height: 100%;
}
.main-header-bg {
overflow: hidden;
position: relative;
.header-theme {
position: absolute;
z-index: 9;
height: 40px;
line-height: 40px;
top: 12px;
right: 30px;
display: flex;
justify-content: flex-end;
.theme {
width: 40px;
height: 40px;
text-align: center;
// @include bg_color($background_color_theme);
}
}
}
</style>
<template>
<div class="layout height100">
<Layout class="height100">
<Sider ref="side1" hide-trigger collapsible :collapsed-width="width" v-model="app.isCollapsed">
<!-- 引入菜单组件 -->
<SideMenu></SideMenu>
</Sider>
<Layout>
<Header :style="{padding: 0}" class="layout-header-bar">
<Row class="main-header-bg">
<Icon @click.native="collapsedSider" :class="rotateIcon" :style="{margin: '0 20px'}" type="md-menu" size="24"></Icon>
<i-col :xs="3" class="header-theme">
<div class="theme" @click="toRoute('theme',{})">theme</div>
</i-col>
</Row>
</Header>
<Content :style="{margin: '20px', background: '#fff', minHeight: '360px',overflow:'auto'}">
<div class="main-bg">
<router-view></router-view>
</div>
</Content>
<Footer>博客园地址:https://i.cnblogs.com/posts</Footer>
</Layout>
</Layout>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
import SideMenu from '@/components/layout/sider'
export default {
data() {
return { }
},
components: {
SideMenu
},
computed: {
...mapState(['app']),
...mapGetters(['rotateIcon', 'menuitemClasses']),
width(state) {
console.log(state.app.pc ? 78 : 0, state.app.pc)
return state.app.pc ? 78 : 0
}
},
methods: {
...mapMutations(['collapsed']),
collapsedSider() {
this.collapsed()
}
},
mounted(){ }
}
</script>

  在layout.vue文件中,重要的部分是组件引入部分以及收起展开的逻辑部分。导航菜单的展开和收起通过操作。动画效果通过store中getters实现,所以在layout.vue中引入了辅助函数。width()方法通过得到store中的pc值判断收起时的宽度。

  store中使用modules中app.js,代码如下:

const moduleApp = {
state: {
isCollapsed: false, // 侧边栏是否折叠,默认不折叠
pc:true // 是否pc端打开
},
mutations: {
collapsed (state) {
// 这里的 `state` 对象是模块的局部状态
state.isCollapsed = !state.isCollapsed
},
// 判断是否pc端,若不是pc端,将自动收起菜单
isPC(state,boo){
state.pc = boo;
if(!boo + '' == 'true'){
state.isCollapsed = true
}
}
},
getters: {
rotateIcon (state, getters, rootState) {
return [
'menu-icon',
state.isCollapsed ? 'rotate-icon' : ''
];
},
menuitemClasses (state, getters, rootState) {
return [
'menu-item',
state.isCollapsed ? 'collapsed-menu' : ''
]
}
},
actions:{
// 测试actions
incrementIfOddOnRootSum ({ state, commit, rootState },param) {
console.log(state,rootState,param)
commit('increment',param.num)
}
}
}
export default moduleApp

  在创建vuex实例时,通过模块化引入app.js为app。

  导航菜单是由路由动态生成,以下是router.js路由文件的代码:

import layout from '@/pages/layout'
import home from '@/pages/home' let routes = [
{
path: '/',
name: 'layout',
size:18, // 图标大小
type: 'md-home', // icon类型
text: '主页', // 文本内容
component: layout,
redirect: '/page1',
meta:{
hidden:false
},
children: [
{
path: 'page1',
name: 'page1',
size:18,
type: 'ios-paper',
text:'首页',
meta: {
hidden:true
},
component: () => import('@/components/HelloWorld.vue')
}
]
},
{
path:'/login',
name:'login',
meta:{
hidden:true
},
component:()=>import('@/components/HelloWorld.vue')
},
{
path: '/component',
name: 'component',
size:18, // 图标大小
type: 'md-cube', // icon类型
text: '组件', // 文本内容
component: layout,
meta: {
hidden:false
},
children: [
{
path: 'other',
name: 'other',
// size:18, // 图标大小
type: 'ios-aperture', // icon类型
text: '二级菜单', // 文本内容
component: home,
meta: {
hidden:false
},
children: [
{
path: 'theme',
name: 'theme',
// size:18, // 图标大小
type: 'ios-brush', // icon类型
text: 'theme', // 文本内容
meta: {
hidden:false
},
component: () => import('@/components/theme.vue')
},
]
},
{
path: 'page2',
name: 'page2',
// size:18, // 图标大小
type: 'md-cafe', // icon类型
text: '单选框自定义样式', // 文本内容
meta: {
hidden:false
},
component: () => import('@/components/input.vue')
}
]
}
]
export default routes

  某个路由是否在导航菜单中显示,通过meta中hidden控制,true表示隐藏,false表示不隐藏。layout和hone为模板页,layout是上面layout.vue文件,layout是一级、二级菜单的模板页,home是三级菜单的模板。引入一般的显示页面通过路由懒加载的方式,当要打开对应页面时,在加载页面。当一级菜单项只有一个子元素时,只会显示一级菜单项不会展开下拉列表,设置子元素的显示(hidden)将无效。例如:主页的children只有一个子元素,这时,设置这个子元素的hidden为false,页面中也不会出现子菜单显示此项。因为判断时一级菜单项的子元素长度需要大于一才会出现子菜单。

  另外,我在app.vue中判断是否pc端,然后修改store中的值。

<template>
<div id="app">
<router-view/>
</div>
</template> <script>
import * as util from '@/libs/util'
import { mapMutations } from 'vuex'
export default {
name: 'App',
methods: {
...mapMutations(['isPC']),
browserRedirect(state) {
var sUserAgent = navigator.userAgent.toLowerCase();
var bIsIpad = sUserAgent.match(/ipad/i) == "ipad";
var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
var bIsMidp = sUserAgent.match(/midp/i) == "midp";
var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
var bIsAndroid = sUserAgent.match(/android/i) == "android";
var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";
if (bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
this.isPC(false)
} else {
this.isPC(true)
}
}
},
mounted() {
this.browserRedirect()
}
}
</script> <style lang='scss'>
html,
body {
width: 100%;
height: 100%;
}
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 100%;
}
</style>

  当然,还可添加其他操作,例如添加权限,动态从后端获取路由等。

  • 总结

  其中在实现导航菜单展开收起(isCollapsed)操作时,我选择将isCollapsed的值存储在store中。目的是,如果以后要进入某一个页面需要将菜单收起来时,可以直接操作store中的值实现。如果不会有这种需求或者对store还不太熟悉的话,可以直接将isCollapsed当做参数传进菜单组件。

  vue的一个很大的特点是组件化,上面关于展开和收起的菜单我写在了一个组件中。也可以将它们拆成组件,这样不显的累赘。

  多有考虑不到之处,欢迎多多指教。

vue、iview动态菜单(可折叠)的更多相关文章

  1. 【ABP】 动态菜单修改过程asp.netcore+vue

    无论用什么框架,第一件事情就是实现动态菜单,从数据库中读取菜单配置项输出前台,网上翻了一大堆翻译文档,也看了官方英文文档,关键点在于如何实现NavigationProvider和在前端调用abp.na ...

  2. 【vue】iView-admin2.0动态菜单路由

    vue项目实现动态路由有俩种方式 一.前端在routers中写好--所有--路由表 <前端控制路由>,登录时根据用户的角色权限来动态的显示菜单路由 二.前端通过调用接口请求拿到当前用户-- ...

  3. vue+element-ui实现无限级动态菜单树

    使用vue+element-ui实现无限级动态菜单 该案例实现主要使用递归的思想,递归对新人来容易迷惑的是自己调用自己,直到满足条件为止,接下来我们就一步一步实现一个动态多级菜单vue组件 搭建项目并 ...

  4. 【vue】iView-admin2.0动态菜单路由【版2】

    依照iView-admin2.0动态菜单路由[版1] 归纳几个节点动态路由获取方式2 ——> easymock假数据 ——> 数据转组件处理.addRoutes ——> localS ...

  5. 循序渐进VUE+Element 前端应用开发(3)--- 动态菜单和路由的关联处理

    在我开发的很多系统里面,包括Winform混合框架.Bootstrap开发框架等系列产品中,我都倾向于动态配置菜单,并管理对应角色的菜单权限和页面权限,实现系统对用户权限的控制,菜单一般包括有名称.图 ...

  6. vue 动态菜单以及动态路由加载、刷新采的坑

    需求: 从接口动态获取子菜单数据 动态加载 要求只有展开才加载子菜单数据 支持刷新,页面显示正常 思路: 一开始比较乱,思路很多.想了很多 首先路由和菜单共用一个全局route, 数据的传递也是通过s ...

  7. vue根据后端菜单自动生成路由(动态路由)

    vue根据后端菜单自动生成路由(动态路由) router.js import Vue from 'vue' import Router from 'vue-router' import store f ...

  8. Vue + iview框架,搭建项目遇到的相关问题记录 - 国际化router.js不能实现

    例子展示: 概述: 最近在使用vue + iview框架进行web开发,并且有一个需求,需要实现web端的国际化,在完成相关配置文件后,发现router.js 中无法配置,并且会出现异常,在经过百度找 ...

  9. Vue 实现动态路由及登录&404页面跳转控制&页面刷新空白解决方案

    Vue实现动态路由及登录&404页面跳转控制&页面刷新空白解决方案   by:授客 QQ:1033553122   开发环境   Win 10   Vue 2.9.6   node-v ...

随机推荐

  1. TCP定时器 之 TIME_WAIT定时器

    概述 在FIN_WAIT_2收到对端发来的FIN,并回复ACK之后,会进入TIME_WAIT状态,此时添加定时器,定时器超时会将tw控制块从ehash和bhash中删除,并且释放tw控制块: 启动定时 ...

  2. linux如何查看目录或文件夹的总大小--du命令

    记录一下如何查看一个目录或文件夹的总大小. 使用du命令的选项-s,可以统计整个目录或文件夹的大小. 例如 du -sk ./ 156k -k表示以KB为单位计算.

  3. FragmentFactory

    import android.support.v4.app.Fragment; import java.util.HashMap; public class FragmentFactory { pri ...

  4. 自定义ListView实现下拉刷新,下拉加载的功能

    package com.loaderman.myrefreshlistviewdemo; import android.content.Context; import android.util.Att ...

  5. Vue+Python 电商实战

    安装webStorm  https://blog.csdn.net/qq_38845858/article/details/89850737 安装NodeJs  http://nodejs.cn/do ...

  6. frei0r-20190715-118a589 编译 frei0r 时注意事项

    如果滤镜是 CPP 编写,需要选择 gcc Thread model: win32 模式,如果选择 posix 模式时,提示错误: 无法找到 dll 文件: frei0r-1.6.1-dlls-201 ...

  7. 老白关于rac性能调优的建议

    RAC应用设计方面需要在底层做很有设计.虽然ORACLE的售前人员总是说RAC的扩展性是透明的,只要把应用分到不同的节点,就可以平滑的扩展系统能力了.而事实上,RAC的CACHE FUSION机制决定 ...

  8. Oracle 无备份情况下的恢复--临时文件/在线重做日志/ORA-00205

    13.5 恢复临时文件 临时文件没有也不应该备份.通过V$TEMPFILE可以找到所有的临时文件. 此类文件的损坏会造成需要使用临时表空间的命令执行失败,不至于造成实例崩溃或session中断.由于临 ...

  9. ibatis使用iterate实现批量插入insert正确写法

    由于想批量入库提升效率,最近实现了ibatis的批量插入,结果一直报错 :StringIndexOutOfBoundsException ,原来是value中的格式不正确. 本人邮箱:techqu@1 ...

  10. AWSome Day简介

    AWSome Day是什么? 它是一场为时一天.结合教育与技术新知的云计算技术免费研讨会.是面向所有开发人员.IT技术人员.或技术/业务领域决策者必备的基础云计算课程.AWS专业级讲师将在现场带领您从 ...