vue实现侧边折叠菜单栏手风琴效果

在我们做管理后台亦或是产品流程时,总是需要一个菜单栏或者导航栏来架起我们的产品架构,那么,如何用vue实现侧边折叠导航栏呢?接下来将一一介绍。

                                       请先看效果演示图。


步骤1:先架构整个页面的布局,撰写html、css和js
HTML代码

<template>
	<div id="main">
		<div class="content">
			<!-- 左边导航栏 -->
			<div class="admin_nav col-lg-3 col-xl-2" v-show="fullWidth">
				<div class = "admin_nav_site">
					<div class="title">
						数据中心后台管理
					</div>
					<ul v-for="(item,index) in navList" :key="index">
						<li @click="handleToTitle(index,item)" @mouseenter="changeTitleColor(index)" @mouseleave="removeTitleColor(index)"
						 ref="title" :class="{active:currentSort == index}" class="col-lg-12">
							<div class="left_icon">
								<i :class="'iconfont '+item.className"></i>
							</div>
							<div class="middle">
								{{item.title}}
							</div>
							<div class="right_icon">
								<i :class="'iconfont '+item.close"></i>
							</div>
						</li>
						<li v-for="(it,idx) in item.secMenu" :key="idx" v-show="item.isSubShow" class="active1">
							<router-link :to="it.path" tag="a">{{it.name}}</router-link>
						</li>
					</ul>
				</div>
				
			</div>
			<!-- 右边内容 -->
			<div class="admin_content col-12 col-md-12 col-lg-12 col-xl-10">
				<div class="admin_header">
					<!-- 导航图标 -->
					<div class="H5_item" v-show="!fullWidth">
						<i class="iconfont icon-daohanglan" @click="make_menu" class-name="H5-menu"></i>
					</div>
					<div class="user">
						<div class="userHead">
							<span>
								<img src="../../assets/image/userhead.svg">
							</span>
							<span>admin</span>
						</div>
						<div class="logOut">
							<i class="iconfont icon-tuichu"></i>
						</div>
					</div>
				</div>
				<div class="admin_body">
					<keep-alive>
						<router-view />
					</keep-alive>
				</div>
			</div>
			
		</div>


	</div>
</template>

JS代码

<script>
	import axios from 'axios';

	export default {
		name: 'navigation',
		data() {
			return {
				navList: [{
						title: '用户管理',
						isSubShow: false,
						className: 'icon-guanliyuan1',
						close: 'icon-close',
						up: 'icon-zhedie',
						secMenu: [
							{
							name: '工号管理',
							path: '/home/navigation-one/user'
							}, 
							// {
							// name: '角色管理',
							// path: '/home/navigation-one/role'
							// }, 
							// {
							// name: '权限管理',
							// path: '/home/navigation-one/user'
							// }, 
						]


					},
					{
						title: '资源管理',
						isSubShow: false,
						className: 'icon-ziyuan',
						close: 'icon-close',
						up: 'icon-zhedie',
						secMenu: [
							{
								name: '我的收藏',
								path: '/home/navigation-one/user'
							},
							{
								name: '我的项目',
								path: '/home/navigation-one/role'
							}
						]

					},
					{
						title: '综合管理',
						isSubShow: false,
						className: 'icon-zonghe',
						close: 'icon-close',
						up: 'icon-zhedie',
						secMenu: [{
								name: '划配管理',
								path: '/home/navigation-one/user'
							},
							{
								name: '数据信息',
								path: '/home/navigation-one/role'
							}
						]

					},
					{
						title: '系统消息',
						isSubShow: false,
						className: 'icon-xiaoxi',
						close: 'icon-close',
						up: 'icon-zhedie',
						secMenu: [{
								name: '实时动态',
								path: '/home/navigation-one/user'
							},
							{
								name: '过去动态',
								path: '/home/navigation-one/role'
							}
						]
					}
				],
				currentSort: 0,
				pagetype: "index",
				fullWidth: true,
				dismenu: true
			}
		},
		methods: {
			active(index) {
				this.currentSort = index;
			},
			changeTitleColor(index) {
				this.currentSort = index;
			},
			removeTitleColor(index) {
				this.currentSort = null;
			},
			handleToTitle(ind, item) {
				this.navList[ind].isSubShow = !this.navList[ind].isSubShow;
				if (this.navList[ind].isSubShow == true) {
					this.navList[ind].close = 'icon-zhedie';
				} else {
					this.navList[ind].close = 'icon-close';
				}
			}
		}

	}
</script>

CSS代码

    #main {
		background: #F0F6FF;
		font-size: 0.16rem;
	}

	#main .content {
		display: flex;
		flex-direction: row;
	}

	.content .admin_nav {
		min-height: 9.37rem;
		max-height: 1.3rem;
		padding: 0 !important;
		background: #364150;
		position: relative;
		
	}
	
	.content .admin_nav .admin_nav_site{
		height: 100vh;
	}

	.content .admin_nav .title {
		height: 1.39rem;
		line-height: 1.39rem;
		background: rgba(63, 73, 86, 1);
		color: #FFFFFF;
		text-align: center;
		font-size: 0.18rem;
		display: flex;
		justify-content: center;
	}

	.content .admin_nav .title div {
		padding: 0 0.03rem;
	}

	.content .admin_nav .title div:first-child i {
		font-size: 0.18rem;
	}

	.content .admin_nav ul .active {
		background: rgba(68, 81, 99, 1);
	}

	.content .admin_nav ul .active1 a.router-link-active {
		color: #fff;
	}

	.content .admin_nav ul li {
		height: 0.6rem;
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		font-size: 0.18rem;
	}

	.content .admin_nav ul li a {
		color: #cecdc5;
		cursor: pointer;
	}

	.content .admin_nav ul li:first-child {
		font-size: 0.2rem;
		height: 0.64rem;
		display: flex;
		flex-direction: row;
		align-items: center;
		color: #BEC5C0;
		cursor: pointer;
	}

	.content .admin_nav ul li:first-child div {
		width: 100%;
		height: 100%;
		display: flex;
		justify-content: center;
		align-items: center;
		color: #BEC5C0;
	}

	.content .admin_nav ul li:first-child .left_icon {
		width: 22%;
	}

	.content .admin_nav ul li:first-child .left_icon i {
		color: #1296DB;
	}

	.content .admin_nav ul li:first-child .middle {
		width: 56%;
		display: flex;
		justify-content: flex-start;
	}

	.content .admin_nav ul li:first-child .right_icon {
		width: 22%;
	}

	.admin_nav .admin .middle:hover {
		color: #fff;
	}

	.admin_nav .admin .right_icon i:hover {
		color: #fff;
	}

	.admin_nav .admin .middle.router-link-active {
		color: #fff;
	}

	.content .admin_nav_position {
		position: absolute;
		z-index: 3000;
	}

	.content .admin_content {
		padding: 0 !important;
	}

	.content .admin_content .admin_header {
		width: 100%;
		height: 0.6rem;
		background: #364150;
		display: flex;
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
	}

	.content .admin_content .admin_header .H5_item {
		width: 0.4rem;
		height: 0.4rem;
		border-radius: 0.2rem;
		display: flex;
		justify-content: center;
		align-items: center;
		margin-left: 0.2rem;
	}

	.content .admin_content .admin_header .H5_item i {
		font-size: 0.22rem;
		color: #fff;
	}

	.content .admin_content .admin_header .user {
		width: 100%;
		display: flex;
		align-items: center;
		justify-content: flex-end;
	}

	.content .admin_content .admin_header .user .userHead {
		height: 0.6rem;
		display: flex;
		flex-direction: row;
		align-items: center;
		color: #fff;
		font-size: 0.18rem;
	}

	.content .admin_content .admin_header .user .userHead span {
		padding: 0 0.05rem;
	}

	.content .admin_content .admin_header .user .userHead span:nth-child(1) {
		position: relative;
	}

	.content .admin_content .admin_header .user .userHead span img {
		width: 0.4rem;
		height: 0.4rem;
		border-radius: 50%;
	}

	.content .admin_content .admin_header .user .userHead span input {
		width: 0.4rem;
		height: 0.4rem;
		position: absolute;
		overflow: hidden;
		opacity: 0;
		cursor: pointer;
	}

	.content .admin_content .admin_header .user .logOut {
		color: #fff;
		margin-left: 0.12rem;
		margin-right: 0.5rem;
	}

	.content .admin_content .admin_header .user .logOut i {
		color: #1296DB;
		font-size: 0.24rem;
		cursor: pointer;
	}

	.toggle-cart-enter-active {
		transition: all 0.4s linear;
	}

	.toggle-cart-leave-active {
		transition: all 0.4s linear;
	}

	.toggle-cart-enter {
		transform: translateX(-200%);
	}

	.toggle-cart-leave-active {
		transform: translateX(-200%);
	}

 

步骤2:撰写完基本网页布局和样式后,对部分细节进行剖析。

1、动态绑定路径

<router-link :to="it.path" tag="a">{{it.name}}</router-link>

2、一级菜单,标题及折叠符号

①通过@mouseenter和@mouseleave事件,当鼠标移入和移出时,改变标题区域背景颜色

②通过@click事件,当鼠标点击时,显示二级菜单列表

③利用:class动态绑定类名,触发active方法,修改折叠符号状态;绑定图标类名,通过遍历,修改对应图标

<ul v-for="(item,index) in navList" :key="index">
	<li @click="handleToTitle(index,item)" @mouseenter="changeTitleColor(index)" @mouseleave="removeTitleColor(index)" ref="title" :class="{active:currentSort == index}" class="col-lg-12">
	    <div class="left_icon">
		    <i :class="'iconfont '+item.className"></i>
	    </div>
	    <div class="middle">
		    {{item.title}}
	    </div>
	    <div class="right_icon">
		    <i :class="'iconfont '+item.close"></i>
	    </div>
	</li>
	<li v-for="(it,idx) in item.secMenu" :key="idx" v-show="item.isSubShow" class="active1">
		<router-link :to="it.path" tag="a">{{it.name}}</router-link>
	</li>
</ul>
active(index) {
	this.currentSort = index;
},

changeTitleColor(index) {
	this.currentSort = index;
},

removeTitleColor(index) {
	this.currentSort = null;
},

handleToTitle(ind, item) {
	this.navList[ind].isSubShow = !this.navList[ind].isSubShow;
	if (this.navList[ind].isSubShow == true) {
		this.navList[ind].close = 'icon-zhedie';
	} else {
		this.navList[ind].close = 'icon-close';
	}
}

步骤3:菜单栏完成后,在网页端能进行查看,但是还需做一个手机端适配,这样就达到两端同步适应的效果。实现如下:

①对左边导航栏再撰写第二套代码,并绑定好对应的参数实现显示和隐藏

②运用vue已有的transition,绑定name值,实现动画效果绑定

HTML代码

<!-- 移动端导航栏 -->
<transition name="toggle-cart">
	<div class="admin_nav admin_nav_position col-8 col-md-5 col-lg-3 col-xl-3" v-show="!fullWidth&&!dismenu">
		<div class="title">
			<div v-show="!fullWidth">
				<i class="iconfont icon-daohanglan" @click="make_menu" class-name="H5-menu"></i>
			</div>
			<div>数据中心后台管理</div>
		</div>
		<ul v-for="(item,index) in navList" :key="index">
			<li @click="handleToTitle(index,item)" @mouseenter="changeTitleColor(index)" @mouseleave="removeTitleColor(index)" ref="title" :class="{active:currentSort == index}">
				<div class="left_icon">
					<i :class="'iconfont '+item.className"></i>
				</div>
				<div class="middle">
					{{item.title}}
				</div>
				<div class="right_icon">
					<i :class="'iconfont '+item.close"></i>
				</div>
			</li>
			<li v-for="(it,idx) in item.secMenu" :key="idx" v-show="item.isSubShow" class="active1">
				<router-link :to="it.path" tag="a">{{it.name}}</router-link>
			</li>
		</ul>
	</div>
</transition>

JS代码

mounted() {
	window.onresize = () => { //监听屏幕变化
		this.page_width();
	};
	this.page_width();
},
methods: {
	make_menu() { //点击导航图标
		this.dismenu = !this.dismenu;
	},
	page_width() { //获取屏幕宽度
		var screenWidth = window.screen.width;
		if (screenWidth <= 1024) {
			this.fullWidth = false;
		} else {
			this.fullWidth = true;
		}
	}
}

最后来看下手机端效果:

附上全文代码:

<template>
	<div id="main">
		<div class="content">
			<!-- 左边导航栏 -->
			<div class="admin_nav col-lg-3 col-xl-2" v-show="fullWidth">
				<div class = "admin_nav_site">
					<div class="title">
						数据中心后台管理
					</div>
					<ul v-for="(item,index) in navList" :key="index">
						<li @click="handleToTitle(index,item)" @mouseenter="changeTitleColor(index)" @mouseleave="removeTitleColor(index)" 		
						ref="title" :class="{active:currentSort == index}" class="col-lg-12">
							<div class="left_icon">
								<i :class="'iconfont '+item.className"></i>
							</div>
							<div class="middle">
								{{item.title}}
							</div>
							<div class="right_icon">
								<i :class="'iconfont '+item.close"></i>
							</div>
						</li>
						<li v-for="(it,idx) in item.secMenu" :key="idx" v-show="item.isSubShow" class="active1">
							<router-link :to="it.path" tag="a">{{it.name}}</router-link>
						</li>
					</ul>
				</div>
				
				<!-- 管理员端 -->
				<!-- <ul class="admin">
					<li class="col-lg-12">
						<div class="left_icon">
							<i class="iconfont icon-guanliyuan1"></i>
						</div>
						<router-link to="/admin/administrator/secadmin" tag="div" class="middle">管理员端</router-link>
						<div class="right_icon">
							<i class="iconfont icon-jinru"></i>
						</div>
					</li>
				</ul> -->
			</div>
			<!-- 右边内容 -->
			<div class="admin_content col-12 col-md-12 col-lg-12 col-xl-10">
				<div class="admin_header">
					<!-- 导航图标 -->
					<div class="H5_item" v-show="!fullWidth">
						<i class="iconfont icon-daohanglan" @click="make_menu" class-name="H5-menu"></i>
					</div>
					<div class="user">
						<div class="userHead">
							<span>
								<img src="../../assets/image/userhead.svg">
							</span>
							<span>admin</span>
						</div>
						<div class="logOut">
							<i class="iconfont icon-tuichu"></i>
						</div>
					</div>
				</div>
				<div class="admin_body">
					<keep-alive>
						<router-view />
					</keep-alive>
				</div>
			</div>
			<!-- 移动端导航栏 -->
			<transition name="toggle-cart">
				<div class="admin_nav admin_nav_position col-8 col-md-5 col-lg-3 col-xl-3" v-show="!fullWidth&&!dismenu">
					<div class="title">
						<div v-show="!fullWidth">
							<i class="iconfont icon-daohanglan" @click="make_menu" class-name="H5-menu"></i>
						</div>
						<div>数据中心后台管理</div>
					</div>
					<ul v-for="(item,index) in navList" :key="index">
						<li @click="handleToTitle(index,item)" @mouseenter="changeTitleColor(index)" @mouseleave="removeTitleColor(index)"
						 ref="title" :class="{active:currentSort == index}">
							<div class="left_icon">
								<i :class="'iconfont '+item.className"></i>
							</div>
							<div class="middle">
								{{item.title}}
							</div>
							<div class="right_icon">
								<i :class="'iconfont '+item.close"></i>
							</div>
						</li>
						<li v-for="(it,idx) in item.secMenu" :key="idx" v-show="item.isSubShow" class="active1">
							<router-link :to="it.path" tag="a">{{it.name}}</router-link>
						</li>
					</ul>
				</div>
			</transition>
		</div>
	</div>
</template>

<script>
	import axios from 'axios';

	export default {
		name: 'navigation-1',
		data() {
			return {
				token: '',
				navList: [{
						title: '用户管理',
						isSubShow: false,
						className: 'icon-guanliyuan1',
						close: 'icon-close',
						up: 'icon-zhedie',
						secMenu: [
							{
							name: '工号管理',
							path: '/home/navigation-one/user'
							}, 
							// {
							// name: '角色管理',
							// path: '/home/navigation-one/role'
							// }, 
							// {
							// name: '权限管理',
							// path: '/home/navigation-one/user'
							// }, 
						]


					},
					{
						title: '资源管理',
						isSubShow: false,
						className: 'icon-ziyuan',
						close: 'icon-close',
						up: 'icon-zhedie',
						secMenu: [
							{
								name: '我的收藏',
								path: '/home/navigation-one/user'
							},
							{
								name: '我的项目',
								path: '/home/navigation-one/role'
							}
						]

					},
					{
						title: '综合管理',
						isSubShow: false,
						className: 'icon-zonghe',
						close: 'icon-close',
						up: 'icon-zhedie',
						secMenu: [{
								name: '划配管理',
								path: '/home/navigation-one/user'
							},
							{
								name: '数据信息',
								path: '/home/navigation-one/role'
							}
						]

					},
					{
						title: '系统消息',
						isSubShow: false,
						className: 'icon-xiaoxi',
						close: 'icon-close',
						up: 'icon-zhedie',
						secMenu: [{
								name: '实时动态',
								path: '/home/navigation-one/user'
							},
							{
								name: '过去动态',
								path: '/home/navigation-one/role'
							}
						]
					}
				],
				currentSort: 0,
				pagetype: "index",
				fullWidth: true,
				dismenu: true
			}
		},
		mounted() {
			window.onresize = () => { //监听屏幕变化
				this.page_width();
			};
			this.page_width();
		},
		methods: {
			active(index) {
				this.currentSort = index;
			},
			changeTitleColor(index) {
				this.currentSort = index;
			},
			removeTitleColor(index) {
				this.currentSort = null;
			},
			handleToTitle(ind, item) {
				this.navList[ind].isSubShow = !this.navList[ind].isSubShow;
				if (this.navList[ind].isSubShow == true) {
					this.navList[ind].close = 'icon-zhedie';
				} else {
					this.navList[ind].close = 'icon-close';
				}
			},
			make_menu() { //点击导航图标
				this.dismenu = !this.dismenu;
			},
			page_width() { //获取屏幕宽度
				var screenWidth = window.screen.width;
				if (screenWidth <= 1024) {
					this.fullWidth = false;
				} else {
					this.fullWidth = true;
				}
			}
		}

	}
</script>

<style scoped>
	#main {
		background: #F0F6FF;
		font-size: 0.16rem;
	}

	#main .content {
		display: flex;
		flex-direction: row;
	}

	.content .admin_nav {
		min-height: 9.37rem;
		max-height: 1.3rem;
		padding: 0 !important;
		background: #364150;
		position: relative;
		
	}
	
	.content .admin_nav .admin_nav_site{
		height: 100vh;
	}

	.content .admin_nav .title {
		height: 1.39rem;
		line-height: 1.39rem;
		background: rgba(63, 73, 86, 1);
		color: #FFFFFF;
		text-align: center;
		font-size: 0.18rem;
		display: flex;
		justify-content: center;
	}

	.content .admin_nav .title div {
		padding: 0 0.03rem;
	}

	.content .admin_nav .title div:first-child i {
		font-size: 0.18rem;
	}

	.content .admin_nav ul .active {
		background: rgba(68, 81, 99, 1);
	}

	.content .admin_nav ul .active1 a.router-link-active {
		color: #fff;
	}

	.content .admin_nav ul li {
		height: 0.6rem;
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		font-size: 0.18rem;
	}

	.content .admin_nav ul li a {
		color: #cecdc5;
		cursor: pointer;
	}

	.content .admin_nav ul li:first-child {
		font-size: 0.2rem;
		height: 0.64rem;
		display: flex;
		flex-direction: row;
		align-items: center;
		color: #BEC5C0;
		cursor: pointer;
	}

	.content .admin_nav ul li:first-child div {
		width: 100%;
		height: 100%;
		display: flex;
		justify-content: center;
		align-items: center;
		color: #BEC5C0;
	}

	.content .admin_nav ul li:first-child .left_icon {
		width: 22%;
	}

	.content .admin_nav ul li:first-child .left_icon i {
		color: #1296DB;
	}

	.content .admin_nav ul li:first-child .middle {
		width: 56%;
		display: flex;
		justify-content: flex-start;
	}

	.content .admin_nav ul li:first-child .right_icon {
		width: 22%;
	}

	.admin_nav .admin .middle:hover {
		color: #fff;
	}

	.admin_nav .admin .right_icon i:hover {
		color: #fff;
	}

	.admin_nav .admin .middle.router-link-active {
		color: #fff;
	}

	.content .admin_nav_position {
		position: absolute;
		z-index: 3000;
	}

	.content .admin_content {
		padding: 0 !important;
	}

	.content .admin_content .admin_header {
		width: 100%;
		height: 0.6rem;
		background: #364150;
		display: flex;
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
	}

	.content .admin_content .admin_header .H5_item {
		width: 0.4rem;
		height: 0.4rem;
		border-radius: 0.2rem;
		display: flex;
		justify-content: center;
		align-items: center;
		margin-left: 0.2rem;
	}

	.content .admin_content .admin_header .H5_item i {
		font-size: 0.22rem;
		color: #fff;
	}

	.content .admin_content .admin_header .user {
		width: 100%;
		display: flex;
		align-items: center;
		justify-content: flex-end;
	}

	.content .admin_content .admin_header .user .userHead {
		height: 0.6rem;
		display: flex;
		flex-direction: row;
		align-items: center;
		color: #fff;
		font-size: 0.18rem;
	}

	.content .admin_content .admin_header .user .userHead span {
		padding: 0 0.05rem;
	}

	.content .admin_content .admin_header .user .userHead span:nth-child(1) {
		position: relative;
	}

	.content .admin_content .admin_header .user .userHead span img {
		width: 0.4rem;
		height: 0.4rem;
		border-radius: 50%;
	}

	.content .admin_content .admin_header .user .userHead span input {
		width: 0.4rem;
		height: 0.4rem;
		position: absolute;
		overflow: hidden;
		opacity: 0;
		cursor: pointer;
	}

	.content .admin_content .admin_header .user .logOut {
		color: #fff;
		margin-left: 0.12rem;
		margin-right: 0.5rem;
	}

	.content .admin_content .admin_header .user .logOut i {
		color: #1296DB;
		font-size: 0.24rem;
		cursor: pointer;
	}

	.toggle-cart-enter-active {
		transition: all 0.4s linear;
	}

	.toggle-cart-leave-active {
		transition: all 0.4s linear;
	}

	.toggle-cart-enter {
		transform: translateX(-200%);
	}

	.toggle-cart-leave-active {
		transform: translateX(-200%);
	}
</style>

注:

1、css样式中的rem换算成px为对应的rem值*100,如:font-size:0.24rem;=>  font-size:24px;

2、文章中涉及到的iconfont类名是因为引用了阿里图标,如遇到不知道如何引用的可以翻阅博文→ https://blog.csdn.net/weixin_44803753/article/details/113423633

3、关于router-link对应的跳转,如有小伙伴不太清楚如何使用vue-router配置路由的可以翻阅博文→https://blog.csdn.net/weixin_44803753/article/details/113436625

Ending(〃'▽'〃)!

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值