文章目录
参考视频:
VUE项目,VUE项目实战,vue后台管理系统,前端面试,前端面试项目
案例链接【前端】Vue+Element UI案例:通用后台管理系统-导航栏(视频p1-16)https://blog.csdn.net/karshey/article/details/127640658【前端】Vue+Element UI案例:通用后台管理系统-Header+导航栏折叠(p17-19)https://blog.csdn.net/karshey/article/details/127652862【前端】Vue+Element UI案例:通用后台管理系统-Home组件:卡片、表格(p20-22)https://blog.csdn.net/karshey/article/details/127674643【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表准备:axios封装、mock数据模拟实战(p23-25)https://blog.csdn.net/karshey/article/details/127735159【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表:折线图、柱状图、饼状图(p27-30)https://blog.csdn.net/karshey/article/details/127737979【前端】Vue+Element UI案例:通用后台管理系统-面包屑、tag栏(p31-35)https://blog.csdn.net/karshey/article/details/127756733【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Form表单填写、Dialog对话框弹出(p36-38)https://blog.csdn.net/karshey/article/details/127787418【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Table表格增删查改、Pagination分页、搜索框(p39-42)https://blog.csdn.net/karshey/article/details/127777962【前端】Vue+Element UI案例:通用后台管理系统-登陆页面Login(p44)https://blog.csdn.net/karshey/article/details/127795302【前端】Vue+Element UI案例:通用后台管理系统-登陆页面功能:登录权限跳转、路由守卫、退出(p45-46)https://blog.csdn.net/karshey/article/details/127849502【前端】Vue+Element UI案例:通用后台管理系统-登陆不同用户显示不同菜单、动态添加路由(p47-48)https://blog.csdn.net/karshey/article/details/127865621【前端】Vue+Element UI案例:通用后台管理系统-项目总结https://blog.csdn.net/karshey/article/details/127867638
目标
- 点击左边的tab栏,如果在面包屑上没有则添加
- 点击面包屑或tag可以进行路由跳转
- tag可以删除
- 若删除的是当前页面,则路由跳转至下一个tag
- 若删除的当前页面是最后一个,则跳转至前一个
- 用vuex完成组件间的通信
代码
0.创建组件、完成路由
首先,我们并没有Mall、User等组件,我们要先创建它们,然后写到路由上。至于有哪些路由,见数据
MenuData
:
const MenuData=[{path:'/',name:'home',label:'首页',icon:'s-home',url:'Home/Home'},{path:'/mall',name:'mall',label:'商品管理',icon:'video-play',url:'MallManage/MallManage'},{path:'/user',name:'user',label:'用户管理',icon:'user',url:'UserManage/UserManage'},{label:'其他',icon:'location',children:[{path:'/page1',name:'page1',label:'页面1',icon:'setting',url:'Other/PageOne'},{path:'/page2',name:'page2',label:'页面2',icon:'setting',url:'Other/PageTwo'}]}]exportdefault MenuData
则router下的index.js如下:
import Vue from"vue";import VueRouter from"vue-router";import Main from'../Views/Main'import Home from'../Views/Home.vue'import Mall from'../Views/Mall.vue'import User from'../Views/User.vue'import PageOne from'../Views/PageOne.vue'import PageTwo from'../Views/PageTwo.vue'
Vue.use(VueRouter)const routes=[// 主路由{path:'/',component:Main,redirect:'/home',// 重定向children:[// 子路由// 这是本次写的部分{path:'/home',name:'home',component: Home },// 首页{path:'/user',name:'user',component: User },// 用户管理{path:'/mall',name:'mall',component: Mall },// 商品管理{path:'/page1',name:'page1',component: PageOne },// 页面1{path:'/page2',name:'page2',component: PageTwo },// 页面2]}]const router =newVueRouter({
routes
})exportdefault router
1.面包屑
面包屑是放在Header的。我们打开Element UI,找到对应的组件:
<el-breadcrumbseparator="/"><el-breadcrumb-item:to="{ path: '/' }">首页</el-breadcrumb-item><el-breadcrumb-item><ahref="/">活动管理</a></el-breadcrumb-item><el-breadcrumb-item>活动列表</el-breadcrumb-item><el-breadcrumb-item>活动详情</el-breadcrumb-item></el-breadcrumb>
写进CommonHeader中:
<divclass="l-content"><el-button@click="handleMenu"icon="el-icon-menu"size="mini"></el-button><!-- 面包屑 --><el-breadcrumbseparator="/"><el-breadcrumb-item:to="{ path: '/' }">首页</el-breadcrumb-item></el-breadcrumb></div>
我们这里要动态绑定路由的数据,点了某个路由才会显示它。
注意:首页是不管怎样都会有的,因此首页的路由数据是写死在vuex的store的state中的,而其他的是动态添加的(Array.push)
2.用Vuex完成数据的通信:从Aside和Header到面包屑和tag
为什么会有组件间数据的通信呢?因为我们点击路由跳转在Aside,显示的面包屑在Header,而tag在每一个组件都要显示,所以它要单独写一个组件放进Main中。
而组件间的通信我们用的是Vuex,这个在之前用过,具体不再赘述。
在store文件夹下的tab.js添加:
tagList
:在state中,用于表示面包屑的数据selectMenu
:在mutation中,用于更新面包屑的数据
exportdefault{state:{isCollapse:false,//导航栏是否折叠tabList:[{path:'/',name:'home',label:'首页',icon:'s-home',url:'Home/Home'}],//面包屑的数据:点了哪个路由,首页是一定有的},mutations:{// 修改导航栏展开和收起的方法CollapseMenu(state){
state.isCollapse =!state.isCollapse
},// 更新面包屑的数据SelectMenu(state, item){// 如果点击的不在面包屑数据中,则添加const index = state.tabList.findIndex(val=> val.name === item.name)if(index ===-1){
state.tabList.push(item)}}}}
要在Aside的侧边栏点击事件处添加面包屑部分代码:
clickItem(item){// 防止自己跳自己的报错if(this.$route.path !== item.path &&!(this.$route.path ==='/home'&&(item.path ==='/'))){this.$router.push(item.path)}// 面包屑this.$store.commit('SelectMenu',item)}
到这里,只要在侧边栏点击了tab,就会产生路由跳转,且点击的数据若是新产生的,则会添加到tagList中。加粗部分是我们上面代码所完成的需求。
接下来我们需要将tagList中的数据在面包屑中显示出来。在Header的面包屑部分绑定数据:v-for对每一个tagList:
<!-- 面包屑 --><el-breadcrumbseparator="/"><el-breadcrumb-itemv-for="item in tags":key="item.path":to="{ path: item.path }">
{{item.label}}
</el-breadcrumb-item></el-breadcrumb>
js:
mapState
是辅助函数,不了解的话可以去看vuex官方文档。由于本篇目的只在于做项目,函数功能不赘述。
import{ mapState }from'vuex'exportdefault{computed:{...mapState({tags:state=> state.tab.tabList
})}}
效果:从上到下把所有tab都点一遍。显然完成了需求,但是样式不对。
3.面包屑样式
面包屑样式需求:
- 和button同一行
- 上下居中
- 和左边button有距离
- 最后一个的字是白色(#fff)
- 其他颜色的字是灰色(#666)
css:
.l-content{display: flex;
// 上下居中
align-items: center;.el-breadcrumb{margin-left: 15px;// deep 强制生效
/deep/.el-breadcrumb__item{.el-breadcrumb__inner{&.is-link{color: #666;}}&:last-child{.el-breadcrumb__inner{color: #fff;}}}}}
效果:
4.tag栏结构
tag栏在任何页面都要出现,说明它要单独写成一个组件CommonTags.vue,并放在Main中。
在Element UI中找到tag组件:
稍微看一下script代码,很明显我们用不到它。
<el-tagv-for="tag in tags":key="tag.name"closable:type="tag.type">
{{tag.name}}
</el-tag>
代码中不了解的属性(Attributes)可以在文档中查一下。红框中为本次会用到的属性:
<template><divclass="tabs"><!-- closable是否可删除:除了"首页"都可删 --><!-- effect:主题,当前主题是dark,其他事plain --><el-tagv-for="item in tags":key="item.path":closable="item.name !== 'home'":effect="item.name === $route.name ? 'dark' : 'plain'">
{{ item.label }}
</el-tag></div></template><script>import{ mapState }from'vuex'exportdefault{computed:{...mapState({tags:state=> state.tab.tabList
})}}</script><style></style>
5.tag事件
- 删除:点击x删除对应的tag和面包屑
- 若删除的是当前页面,则路由跳转至下一个tag
- 若删除的当前页面是最后一个,则跳转至前一个
- 点击:点击哪个tag就跳转到哪个tag
这两个事件分别为:(第一万次感叹,组件真好用)
html:
<el-tagv-for="(item, index) in tags":key="item.path":closable="item.name !== 'home'":effect="item.name === $route.name ? 'dark' : 'plain'"@click="changeMenu(item)"@close="handleClose(item, index)">
{{ item.label }}
</el-tag>
点击事件:
changeMenu(item){this.$router.push({name: item.name })}
删除事件:
handleClose(item, index){// 删除面包屑数据this.$store.commit('closeTag', item)// 如果删除的刚好是自己if(item.name ===this.$route.name){const length =this.tags.length
// 如果删除的是最后一个:跳到前一个if(length === index){this.$router.push({name:this.tags[index -1].name })}// 不是最后一个:往后一个else{this.$router.push({name:this.tags[index].name })}}}
store中的tab.js,在mutation里:
// 删除tag:删除tabList中对应的itemcloseTag(state, item){// 要删除的是state.tabList中的itemconst index = state.tabList.findIndex(val=> val.name === item.name)
state.tabList.splice(index,1)}
6.tag样式
.tabs{padding: 20px;.el-tag{margin-right: 15px;
// 鼠标悬停:小手
cursor: pointer;}}
效果
总代码
本篇修改或新建的文件
CommonTags.vue
<template><divclass="tabs"><!-- closable是否可删除:除了"首页"都可删 --><!-- effect:主题,当前主题是dark,其他事plain --><el-tagv-for="(item, index) in tags":key="item.path":closable="item.name !== 'home'":effect="item.name === $route.name ? 'dark' : 'plain'"@click="changeMenu(item)"@close="handleClose(item, index)">
{{ item.label }}
</el-tag></div></template><script>import{ mapState }from'vuex'exportdefault{methods:{changeMenu(item){this.$router.push({name: item.name })},handleClose(item, index){// 删除面包屑数据this.$store.commit('closeTag', item)// 如果删除的刚好是自己if(item.name ===this.$route.name){const length =this.tags.length
// 如果删除的是最后一个:跳到前一个if(length === index){this.$router.push({name:this.tags[index -1].name })}// 不是最后一个:往后一个else{this.$router.push({name:this.tags[index].name })}}}},computed:{...mapState({tags:state=> state.tab.tabList
})}}</script><stylelang="less"scoped>.tabs{padding: 20px;.el-tag{margin-right: 15px;
// 鼠标悬停:小手
cursor: pointer;}}</style>
tab.js
exportdefault{state:{isCollapse:false,//导航栏是否折叠tabList:[{path:'/',name:'home',label:'首页',icon:'s-home',url:'Home/Home'}],//面包屑的数据:点了哪个路由,首页是一定有的},mutations:{// 修改导航栏展开和收起的方法CollapseMenu(state){
state.isCollapse =!state.isCollapse
},// 更新面包屑的数据SelectMenu(state, item){// 如果点击的不在面包屑数据中,则添加const index = state.tabList.findIndex(val=> val.name === item.name)if(index ===-1){
state.tabList.push(item)}},// 删除tag:删除tabList中对应的itemcloseTag(state, item){// 要删除的是state.tabList中的itemconst index = state.tabList.findIndex(val=> val.name === item.name)
state.tabList.splice(index,1)}}}
router的index.js
import Vue from"vue";import VueRouter from"vue-router";import Main from'../Views/Main'import Home from'../Views/Home.vue'import Mall from'../Views/Mall.vue'import User from'../Views/User.vue'import PageOne from'../Views/PageOne.vue'import PageTwo from'../Views/PageTwo.vue'
Vue.use(VueRouter)const routes=[// 主路由{path:'/',component:Main,redirect:'/home',// 重定向children:[// 子路由{path:'/home',name:'home',component: Home },// 首页{path:'/user',name:'user',component: User },// 用户管理{path:'/mall',name:'mall',component: Mall },// 商品管理{path:'/page1',name:'page1',component: PageOne },// 页面1{path:'/page2',name:'page2',component: PageTwo },// 页面2]}]const router =newVueRouter({
routes
})exportdefault router
版权归原作者 karshey 所有, 如有侵权,请联系我们删除。