spa项目开发之tab页实现
spa项目tab页实现
实现思路及细节
1、利用前面博客所讲的Vuex的知识;定义几个变量
Options:存放tab页对象的容器(主要是路由路径以及tab页的名字)
activeIndex:被激活的tab页路由路径
showName:tab页的标题
Role:用来区分是否是因为左侧菜单被点击造成的路由路径发生改变;
是:pass;不是:nopass
2、左侧导航菜单绑定点击事件
将被点击的菜单名称存放到Vuex中,供路由路径变化监听时,tab页标题显示;
标记一下role为pass,到时新增tab页的时候需要作为判断依据
3、右侧对tab页进行操作
Tab页的点击(切换到被点击的tab页完成路由跳转;标记一下role为nopass,到时新增tab页的时候需要作为判断依据;);
Tab页的移除(删除指定的tab页对象;如果删除的tab页对象处于选中状态,需要将选中状态的下标移到最后一个,
因为原来选中的tab页已经删除了;标记一下role为nopass,到时新增tab页的时候需要作为判断依据;))
4、监听路由路径变化
点亮已经存在的tab页(Vuex中showName与option中的哪个tab页对象的name相同,那么就点亮哪一个)
新增tab页(首先路由路径的变化是因为左侧栏的点击,其次要option中不存在的tab页对象)
相关代码
State.js
1 export default { 2 resturantName: '天天餐馆', 3 jwt:'', 4 options: [],//存放tab页的容器 5 activeIndex: '',//激活的tab页路由路径 6 showName:'show',//tab页的标题 7 role:""//用来区分是否是因为左侧菜单被点击造成的路由路径发生改变,是:pass;不是:nopass 8 // verificationJwt:null, //这是用来保存用户等登录验证码jwt身份识别的 9 }
Mutations.js
1 export default { 2 setResturantName: (state, payload) => { 3 state.resturantName = payload.resturantName; 4 }, 5 setJwt: (state, payload) => { 6 state.jwt = payload.jwt; 7 }, 8 9 setVerificationJwt: (state, payload) => { 10 state.verificationJwt = payload.verificationJwt; 11 }, 12 13 14 // 添加tabs(data包含了路由路径跟tab页名字) 15 add_tabs(state, data) { 16 this.state.options.push(data); 17 }, 18 // 删除tabs (route是路由路径) 19 delete_tabs(state, route) { 20 let index = 0; 21 for (let option of state.options) { 22 if (option.route === route) { 23 break; 24 } 25 index++; 26 } 27 this.state.options.splice(index, 1); //删除options里面下标为Index的一个数 28 }, 29 // 设置当前激活的tab 30 set_active_index(state, index) { 31 this.state.activeIndex = index; 32 }, 33 //设置tab页显示标题 34 set_showName(state, name) { 35 this.state.showName = name; 36 }, 37 set_role(state, role) { 38 this.state.role = role; 39 } 40 41 42 }
Getters.js
1 export default { 2 getResturantName: (state) => { 3 return state.resturantName; 4 }, 5 getJwt: (state) => { 6 return state.jwt; 7 }, 8 9 // getVerificationJwt:(state) =>{ 10 // return state.verificationJwt; 11 // }, 12 13 getShowName:(state) => { 14 return state.showName; 15 }, 16 getOptions:(state) => { 17 return state.options; 18 }, 19 getRole:(state) =>{ 20 return state.role; 21 } 22 23 24 }
LeftNav.vue
1 <template> 2 <el-menu router :default-active="$route.path" default-active="2" class="el-menu-vertical-demo" background-color="#334157" 3 text-color="#fff" active-text-color="#ffd04b" :collapse="collapsed"> 4 <!-- <el-menu default-active="2" :collapse="collapsed" collapse-transition router :default-active="$route.path" unique-opened class="el-menu-vertical-demo" background-color="#334157" text-color="#fff" active-text-color="#ffd04b"> --> 5 <div class="logobox"> 6 <img class="logoimg" src="../assets/img/logo.png" alt=""> 7 </div> 8 <el-submenu :index="'id_'+m.treeNodeId" v-for="m in menus"> 9 <template slot="title"> 10 <i :class="m.icon"></i> 11 <span>{{m.treeNodeName}}</span> 12 </template> 13 <el-menu-item :key="'id_'+m2.treeNodeId" :index="m2.url" @click="showName(m2.treeNodeName)" v-for="m2 in m.children"> 14 <i :class="m2.icon"></i> 15 <span>{{m2.treeNodeName}}</span> 16 </el-menu-item> 17 </el-submenu> 18 </el-menu> 19 </template> 20 <script> 21 export default { 22 data() { 23 return { 24 collapsed: false, 25 menus: [] 26 } 27 }, 28 created() { 29 this.$root.Bus.$on('collapsed-side', (v) => { 30 this.collapsed = v; 31 }) 32 33 let url = this.axios.urls.SYSTEM_MENU_TREE; 34 // let url = 'http://localhost:8080/T216_SSH/vue/userAction_login.action'; 35 this.axios.post(url, {}).then((response) => { 36 console.log(response); 37 this.menus = response.data.result; 38 }).catch(function(error) { 39 console.log(error); 40 }); 41 }, 42 methods: { 43 showName(name) { 44 // 把菜单名称放进去,当成tab页的名称 45 this.$store.commit('set_showName', name) 46 this.$store.commit('set_role', "pass"); 47 } 48 } 49 } 50 </script> 51 <style> 52 .el-menu-vertical-demo:not(.el-menu--collapse) { 53 width: 240px; 54 min-height: 400px; 55 } 56 57 .el-menu-vertical-demo:not(.el-menu--collapse) { 58 border: none; 59 text-align: left; 60 } 61 62 .el-menu-item-group__title { 63 padding: 0px; 64 } 65 66 .el-menu-bg { 67 background-color: #1f2d3d !important; 68 } 69 70 .el-menu { 71 border: none; 72 } 73 74 .logobox { 75 height: 40px; 76 line-height: 40px; 77 color: #9d9d9d; 78 font-size: 20px; 79 text-align: center; 80 padding: 20px 0px; 81 } 82 83 .logoimg { 84 height: 40px; 85 } 86 </style>
AppMain.vue
1 <template> 2 <el-container class="main-container"> 3 <el-aside v-bind:class="asideClass"> 4 <LeftNav></LeftNav> 5 </el-aside> 6 <el-container> 7 <el-header class="main-header"> 8 <TopNav></TopNav> 9 </el-header> 10 <div class="template-tabs"> 11 <el-tabs v-model="activeIndex" type="border-card" closable @tab-click="tabClick" @tab-remove="tabRemove"> 12 <el-tab-pane :key="item.name" v-for="(item, index) in options" :label="item.name" :name="item.route"> 13 </el-tab-pane> 14 </el-tabs> 15 </div> 16 <el-main class="main-center"> 17 <router-view></router-view> 18 </el-main> 19 </el-container> 20 </el-container> 21 </template> 22 23 <script> 24 // 导入组件 25 import TopNav from '@/components/TopNav.vue' 26 import LeftNav from '@/components/LeftNav.vue' 27 28 29 // 导出模块 30 export default { 31 data(){ 32 return { 33 asideClass : 'main-aside' 34 } 35 }, 36 components:{ 37 TopNav,LeftNav 38 }, 39 created() { 40 this.$root.Bus.$on('collapsed-side',(v)=>{ 41 this.asideClass = v ? 'main-aside-collapsed':'main-aside'; 42 }) 43 }, 44 methods: { 45 // tab切换时,动态的切换路由 46 tabClick(tab) { 47 // v-model="activeIndex"是路由路径 48 let path = this.activeIndex; 49 this.$router.push({ path: path }); 50 this.$store.commit('set_role',"nopass"); 51 }, 52 tabRemove(targetName) { 53 // console.log(targetName);targetName是路由路径 54 this.$store.commit('set_role',"nopass"); 55 // let tabs = this.editableTabs; 56 this.$store.commit('delete_tabs', targetName); 57 // 如果激活tab页被关闭,那么需要激活别的tab页,最后一个tab页被关闭,那么跳转主界面 58 if (this.activeIndex === targetName) { 59 // 设置当前激活的路由 60 if (this.options && this.options.length >= 1) { 61 this.$store.commit('set_active_index', this.options[this.options.length - 1].route); 62 this.$router.push({ path: this.activeIndex }); 63 } 64 else { 65 this.$router.push({ path: '/AppMain' }); 66 } 67 } 68 } 69 }, 70 watch: { 71 '$route'(to) { 72 // 只要路由发生改变,就会触发此事件(点击左侧菜单时会触发,删除右侧tab页会触发,切换右侧已存在的tab页会触发) 73 let role=this.$store.state.role; 74 let showName=this.$store.getters.getShowName 75 let flag = false;//判断是否页面中是否已经存在该路由下的tab页 76 //options记录当前页面中已存在的tab页 77 for (let option of this.options) { 78 //用名称匹配,如果存在即将对应的tab页设置为active显示桌面前端 79 if (option.name === showName) { 80 flag = true; 81 this.$store.commit('set_active_index', to.path); 82 break; 83 } 84 } 85 //如果不存在,则新增tab页,再将新增的tab页设置为active显示在桌面前端 86 // if(role!='nopass'){} 87 if(role=='pass'){ 88 if (!flag) { 89 this.$store.commit('add_tabs', { route: to.path, name: showName}); 90 this.$store.commit('set_active_index', to.path); 91 } 92 } 93 } 94 }, 95 computed: { 96 options() { 97 return this.$store.state.options; 98 }, 99 //动态设置及获取当前激活的tab页 100 activeIndex: { 101 get() { 102 return this.$store.state.activeIndex; 103 }, 104 set(val) { 105 this.$store.commit('set_active_index', val); 106 } 107 } 108 } 109 }; 110 </script> 111 <style type="text/css"> 112 .el-tabs--border-card>.el-tabs__content { 113 padding: 0px; 114 } 115 </style> 116 <style scoped> 117 .main-container { 118 height: 100%; 119 width: 100%; 120 box-sizing: border-box; 121 } 122 123 .main-aside-collapsed { 124 /* 在CSS中,通过对某一样式声明! important ,可以更改默认的CSS样式优先级规则,使该条样式属性声明具有最高优先级 */ 125 width: 64px !important; 126 height: 100%; 127 background-color: #334157; 128 margin: 0px; 129 } 130 131 .main-aside { 132 width: 240px !important; 133 height: 100%; 134 background-color: #334157; 135 margin: 0px; 136 } 137 138 .main-header, 139 .main-center { 140 padding: 0px; 141 border-left: 2px solid #333; 142 } 143 </style>
数据库中添加一个新的子tab页数据
在src/sys/目录下创建一个comment.vue
comment.vue相关代码
1 <template> 2 <div> 3 <el-tabs :tab-position="tabPosition" style="height: 200px;"> 4 <el-tab-pane label="游客评论">游客评论管理</el-tab-pane> 5 <el-tab-pane label="普通会员评论">普通会员评论管理</el-tab-pane> 6 <el-tab-pane label="VIP会员评论">VIP会员评论管理</el-tab-pane> 7 <el-tab-pane label="SVIP会员评论">SVIP会员评论管理</el-tab-pane> 8 </el-tabs> 9 </div> 10 </template> 11 12 <script> 13 export default { 14 data() { 15 return { 16 tabPosition: '评论管理' 17 }; 18 } 19 } 20 </script> 21 22 <style> 23 24 </style>
最后配置一下路由中的index.js
router/index.js
1 import Vue from 'vue' 2 import Router from 'vue-router' 3 import HelloWorld from '@/components/HelloWorld' 4 import login from '@/views/login' 5 import Reg from '@/views/Reg' 6 import AppMain from '@/components/AppMain' 7 import LeftNav from '@/components/LeftNav' 8 import TopNav from '@/components/TopNav' 9 import Articles from '@/views/sys/Articles' 10 import VuexPage1 from '@/views/sys/VuexPage1' 11 import VuexPage2 from '@/views/sys/VuexPage2' 12 import comment from '@/views/sys/comment' 13 14 15 Vue.use(Router) 16 17 export default new Router({ 18 routes: [{ 19 path: '/', 20 name: 'login', 21 component: login 22 }, 23 { 24 path: '/login', 25 name: 'login', 26 component: login 27 }, 28 { 29 path: '/Reg', 30 name: 'Reg', 31 component: Reg 32 }, 33 { 34 path: '/AppMain', 35 name: 'AppMain', 36 component: AppMain, 37 children: [{ 38 path: '/LeftNav', 39 name: 'LeftNav', 40 component: LeftNav 41 }, 42 { 43 path: '/TopNav', 44 name: 'TopNav', 45 component: TopNav 46 }, 47 { 48 path: '/sys/Articles', 49 name: 'Articles', 50 component: Articles 51 }, 52 { 53 path: '/sys/VuexPage1', 54 name: 'VuexPage1', 55 component: VuexPage1 56 }, 57 { 58 path: '/sys/VuexPage2', 59 name: 'VuexPage2', 60 component: VuexPage2 61 }, 62 { 63 path: '/sys/comment', 64 name: 'comment', 65 component: comment 66 } 67 68 ] 69 } 70 ] 71 })
效果展示
谢谢观看!!!