vue如何點(diǎn)擊多個(gè)tab標(biāo)簽打開(kāi)關(guān)閉多個(gè)頁(yè)面
點(diǎn)擊多個(gè)tab標(biāo)簽打開(kāi)關(guān)閉多個(gè)頁(yè)面
需求
現(xiàn)將頁(yè)面分為Header LeftSideBar Main三大模塊 左側(cè)LeftSideBar為menu菜單,點(diǎn)擊菜單每一項(xiàng),在Main中出現(xiàn)上部為tag標(biāo)簽,下部為內(nèi)容 可打開(kāi)多個(gè)tag標(biāo)簽 ,可內(nèi)容切換 ,可關(guān)閉
效果圖
1.router.js中(在LeftSideBar組件中現(xiàn)在有兩個(gè)菜單項(xiàng)icons和tabs)
{ path:'/addtab', redirect:'/addtab/index',//重定向 輸入/addtab 地址會(huì)變?yōu)?addtab/index component: Main, children:[ { path:'index',//當(dāng)addtab/index匹配成功時(shí) TabContent會(huì)被渲染在Main中的router-view中 name:'TabContent', component:()=>import("@/components/TabContent") } ] }, { path:'/addicon', redirect:'/addicon/index', component: Main, children:[ { path:'index', name:'IconContent', component:()=>import("@/components/IconContent") } ] }
請(qǐng)戳--動(dòng)態(tài)組件嵌套路由
2.this.$router.push({name:"TabContent",params:{}}) 實(shí)現(xiàn)點(diǎn)擊左側(cè)tab 打開(kāi)組件main
3.在main中
<template> <div> <TagsView/> <router-view></router-view> </div> </template>
4.在TagsView中
<template> <div class="tags-view-wrapper"> <router-link class="tags-view-item" :to="item" :key="item.path" :class="isActive(item)?'active':''" v-for="(item) in Array.from(visitedViews)"> {{item.params.name}} <span class='el-icon-close' @click.prevent.stop='closeSelectedTag(item)'></span> </router-link> </div> </template>
a.添加標(biāo)簽的方法
visitedViews是存放路由信息的數(shù)組
addTags(){ const route=this.$route;//獲取地址欄路由 this.$store.commit({ type:'addTags', route }) } 在store.js中 addTags(state, payload) { let flag = state.visitedTags.some( item => item.path === payload.route.path );//打開(kāi)標(biāo)簽后,判斷數(shù)組中是否已經(jīng)存在該路由 if (!flag) { state.visitedTags.push( Object.assign( {}, { path: payload.route.path, name: payload.route.name, params: payload.route.params } ) ); } //數(shù)組中路由存在不push ,單擊左側(cè)路由變化,點(diǎn)擊標(biāo)簽路由變化均觸發(fā) } //添加標(biāo)簽
第一次點(diǎn)擊是在mountd中觸發(fā)addTags方法,后來(lái)的每次點(diǎn)擊路由均會(huì)變化 ,使用watch監(jiān)聽(tīng)觸發(fā)
watch:{ $route(){ this.addTags(); }//地址欄變化了就觸發(fā)這個(gè)添加方法 }
b.關(guān)閉標(biāo)簽
在store.js中
?closeTags(state, payload) { ? ? ? for (const [key, item] of state.visitedTags.entries()) { ? ? ? ? if (item.path === payload.view.path) { ? ? ? ? ? state.visitedTags.splice(key, 1); ? ? ? ? ? break; ? ? ? ? } ? ? ? } ? ? } //如果要關(guān)閉的標(biāo)簽在路由中存在則刪除 ? ??
在tagviews中
? ? ?isActive(route) { ? ? ? ? return route.path === this.$route.path ? ? ? },//當(dāng)前地址欄路徑是否與渲染的路徑相同 樣式匹配 ? ? ? closeSelectedTag(view){ ? ? ? ? ?this.$store.dispatch({ ? ? ? ? ? ?type:"closeTags", ? ? ? ? ? ?view ? ? ? ? ?}).then((views)=>{ ? ? ? ? ? ? 此時(shí)的views是指的被刪除后的visitedViews數(shù)組中存在的元素 ? ? ? ? ? ?if (this.isActive(view)) { ? ? ? ? ? ? 當(dāng)前關(guān)閉的標(biāo)簽是否是被選中的標(biāo)簽 ? ? ? ? ? ? ?const latestView = views.slice(-1)[0]; ? ? ? ? ? ? ?if (latestView) { ? ? ? ? ? ? ? ?this.$router.push(latestView);//如果數(shù)組不為空則讓選中的標(biāo)簽為緊鄰關(guān)閉標(biāo)簽的那一個(gè) ? ? ? ? ? ? ?} else { ? ? ? ? ? ? ? ?this.$router.push('/') ;//如果為空則頁(yè)面跳轉(zhuǎn)到/ ? ? ? ? ? ? ?} ? ? ? ? ? ?} ? ? ? ? ?}) ? ? ? }
說(shuō)一下思路
點(diǎn)擊左側(cè)的每一項(xiàng)都會(huì)打開(kāi)一個(gè)組件(對(duì)應(yīng)一個(gè)路由) 第一次點(diǎn)擊時(shí)將路由信息push到visitedViews中 后來(lái)的每次點(diǎn)擊都是通過(guò)watch $route執(zhí)行添加標(biāo)簽方法
刪除時(shí)要考慮是否是對(duì)激活項(xiàng)進(jìn)行關(guān)閉 若是則先刪除數(shù)組中要關(guān)閉的標(biāo)簽的那個(gè)路由,然后獲取剩余visitedViews中的路由,讓最后一個(gè)路由作為激活項(xiàng)
vue tab頁(yè)多頁(yè)面切換
實(shí)現(xiàn)路由發(fā)生變化時(shí),新增一個(gè)tab標(biāo)簽頁(yè),點(diǎn)擊其他標(biāo)簽時(shí)切換到對(duì)應(yīng)的頁(yè)面,刷新網(wǎng)頁(yè)同時(shí)保留狀態(tài)
這里就直接說(shuō)它實(shí)現(xiàn)的代碼就OK!?。?/p>
VueX記錄下每次新增后的tab標(biāo)簽頁(yè)路由
store.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { // 路由導(dǎo)航start // 緩存組件頁(yè)面 catch_components: [], // 當(dāng)前選中的菜單 - 默認(rèn)選擇首頁(yè) activePath: '/index', // 菜單項(xiàng) - 默認(rèn)需要展示的頁(yè)面() tabList: [{ path: '/index', label: '首頁(yè)', name: 'index', fullPath: "/index" }], // 路由導(dǎo)航end }, // 更改vuex的store中狀態(tài)的唯一方法 - 同步操作 mutations: { // 路由導(dǎo)航start //清空vuex數(shù)據(jù) clearVUEX(state) { state.catch_components = [] state.activePath = 'index' state.tabList = [{ path: '/idnex', label: '首頁(yè)', name: 'index', fullPath: "/index" }] }, // 跳轉(zhuǎn)頁(yè)面執(zhí)行 selectMenu(state, submenu) { // 首頁(yè)就是 wellcome 也就是 home if (submenu.name === 'index') { submenu.name = 'index' label.path = '首頁(yè)' submenu.path = '/index' submenu.fullPath= '/index' } // 當(dāng)前選中菜單 var activePath = submenu.name // 歷史已選中菜單列表 var oldTabList = state.tabList // 將菜單信息添加到tablist - 添加時(shí)判斷是否已有該路由標(biāo)簽 var result = oldTabList.some(item => { if (item.name === activePath) { // console.log('--------', item.fullPath != submenu.fullPath) // 有該路由標(biāo)簽是否為多次點(diǎn)擊(相當(dāng)于查看同路由下的詳情,該過(guò)程只改變了參數(shù)) if (!item.fullPath != submenu.fullPath) { item.fullPath = submenu.fullPath } return true } }) // 如果不包含該對(duì)象,則添加 if (!result) { oldTabList.push({ path: submenu.name, name: submenu.name, label: submenu.label, fullPath: submenu.fullPath }) } // 重新賦值標(biāo)簽路由和當(dāng)前選中菜單 state.activePath = activePath state.tabList = oldTabList }, // 添加keepalive緩存 addKeepAliveCache(state, val) { // 如果是首頁(yè)不緩存 if (val === 'index') { return } // console.log(state.catch_components) // 添加時(shí)判斷,如果該組件已存在,便不添加 if (state.catch_components.indexOf(val) === -1) { // 不存在,緩存頁(yè)面 state.catch_components.push(val) } }, // 刪除keepalive緩存 removeKeepAliveCache(state, val) { let cache = state.catch_components for (let i = 0; i < cache.length; i++) { if (cache[i] === val) { cache.splice(i, 1); } } state.catch_components = cache }, //關(guān)閉菜單 closeTab(state, val) { // 重新賦值 state.activePath = val.activePath state.tabList = val.tabList }, // 點(diǎn)擊標(biāo)簽選擇菜單 changeMenu(state, val) { state.activePath = val }, // 路由導(dǎo)航end }, actions: { } })
根據(jù)自己的需求定義一個(gè)展示路由標(biāo)簽組件vue文件
BScroll :當(dāng)路由標(biāo)簽過(guò)多時(shí),用于橫向滾動(dòng)標(biāo)簽頁(yè)
<!-- crumbs.vue --> <template> <div class="tags"> <div class="horizontal-container"> <div class="scroll-wrapper" ref="scroll"> <div class="scroll-content"> <el-tag size="medium" v-for="(tab, index) in tabList" :key="tab.path" @close="handleClose(tab, index)" @click="changeMenu(tab)" :closable="tab.name !== 'index'" :effect="activePath === tab.name ? 'dark' : 'plain'"> {{tab.label}} </el-tag> </div> </div> </div> </div> </template> <script> import { mapState } from 'vuex'; import BScroll from '@better-scroll/core' export default { data() { return { //菜單列表 menuList: [], } }, computed: { ...mapState({ // 從 state 中的到的計(jì)算屬性 activePath: state => state.activePath, // 已選中菜單 tabList: state => state.tabList, // tags菜單列表 catch_components: state => state.catch_components, // keepalive緩存 }) }, mounted() { // this.handleCommand() this.init() }, methods: { init() { this.bs = new BScroll(this.$refs.scroll, { scrollX: true, probeType: 3 // listening scroll event }) }, // 清空當(dāng)前vuex數(shù)據(jù) handleCommand() { this.$store.commit('clearVUEX') }, // 點(diǎn)擊菜單 - 傳入name,添加到keepalive緩存頁(yè)面 selectMenu(item) { // console.log(item.name) // 加入keepalive緩存 this.$store.commit('addKeepAliveCache', item.name) //添加tags標(biāo)簽 //訪問(wèn)wellcome 就代表home var name = item.name === 'index' ? 'index' : item.name var submenu = { path: item.path, name: name, label: item.meta.title, fullPath: item.fullPath } // console.log(submenu) //更新選中菜單 this.$store.commit('selectMenu', submenu) console.log(this.$store.state.tabList) }, // 點(diǎn)擊標(biāo)簽跳轉(zhuǎn)路由 changeMenu(item) { // 歷史選中菜單 var oldActivePath = this.$store.state.activePath // 首先判斷點(diǎn)擊的是否是自己,如果是自己則return if (oldActivePath === item.name) { return } // 存儲(chǔ)菜單 this.$store.commit('changeMenu', item.name) // 頁(yè)面跳轉(zhuǎn) this.$router.push({ path: item.fullPath }) }, // 關(guān)閉tab標(biāo)簽 handleClose(tab, index) { // 歷史選中菜單 var oldActivePath = this.$store.state.activePath // 歷史已選中菜單列表 var oldTabList = this.$store.state.tabList // 計(jì)算標(biāo)簽個(gè)數(shù) let length = oldTabList.length - 1 // 刪除tabList中的該對(duì)象 for (let i = 0; i < oldTabList.length; i++) { let item = oldTabList[i] if (item.name === tab.name) { oldTabList.splice(i, 1); } } // 刪除keepAlive緩存 this.$store.commit('removeKeepAliveCache', tab.name) // 如果關(guān)閉的標(biāo)簽不是當(dāng)前路由的話,就不跳轉(zhuǎn) if (tab.name !== oldActivePath) { return } // 如果length為1,必然只剩下首頁(yè)標(biāo)簽,此時(shí)關(guān)閉后,更新到首頁(yè) if (length === 1) { // 同時(shí)存儲(chǔ)菜單 this.$store.commit('closeTab', { activePath: 'home', tabList: oldTabList }) // tab頁(yè)向左跳轉(zhuǎn) this.$router.push({ name: oldTabList[index - 1].name }) // 不再向下執(zhí)行 return } // 關(guān)閉的標(biāo)簽是最右邊的話,往左邊跳轉(zhuǎn)一個(gè) if (index === length) { // 同時(shí)更新路徑 oldActivePath = oldTabList[index - 1].name // 同時(shí)存儲(chǔ)菜單 this.$store.commit('closeTab', { activePath: oldActivePath, tabList: oldTabList }) // tab頁(yè)向左跳轉(zhuǎn) this.$router.push({ name: oldTabList[index - 1].name }) } else { // 同時(shí)更新路徑 oldActivePath = oldTabList[index].name // 同時(shí)存儲(chǔ)菜單 this.$store.commit('closeTab', { activePath: oldActivePath, tabList: oldTabList }) // tab頁(yè)向右跳轉(zhuǎn) this.$router.push({ name: oldTabList[index].name }) } }, }, watch: { // 路由發(fā)生變化時(shí)調(diào)用更新tab標(biāo)簽數(shù)據(jù) '$route': { handler(newValue) { // console.log(newValue, oldValue) this.selectMenu(newValue); }, immediate: true } }, } </script> <style lang="less" scoped="scoped"> /deep/ .el-tag--medium { margin-right: 10px; } .horizontal-container { .scroll-wrapper { position: relative; width: 100%; // margin: 80px auto; margin: 0 auto; white-space: nowrap; // border: 3px solid #42b983; border-radius: 5px; overflow: hidden; .scroll-content { display: inline-block; } .scroll-item { height: 40px; line-height: 40px; // font-size: 24px; display: inline-block; text-align: center; padding: 0 10px; } } } /deep/.el-tabs__nav-scroll { background: #fff; } .el-tag { cursor: pointer; margin-left: 10px; border-radius: 2px; font-size: 12px; color: #1890FF; border-color: #1890FF; } .el-tag--dark { color: #fff; background-color: #1890FF; } .el-dropdown-link { cursor: pointer; } .el-icon-arrow-down { font-size: 12px; } .submit-row { display: flex; flex-direction: row; justify-content: flex-end; align-items: center; } </style>
若F5或者強(qiáng)刷新頁(yè)面時(shí)需要保留當(dāng)前tab路由數(shù)據(jù),在App.vue中插入代碼
created() { //在頁(yè)面刷新時(shí)將vuex里的信息保存到sessionStorage里 window.addEventListener("beforeunload", () => { sessionStorage.setItem("store", JSON.stringify(this.$store.state)) }) },
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue動(dòng)態(tài)添加表單validateField驗(yàn)證功能實(shí)現(xiàn)
這篇文章主要介紹了vue動(dòng)態(tài)添加表單validateField驗(yàn)證功能實(shí)現(xiàn),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04vue實(shí)現(xiàn)抖音時(shí)間轉(zhuǎn)盤(pán)
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)抖音時(shí)間轉(zhuǎn)盤(pán),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09vuejs+element UI點(diǎn)擊編輯表格某一行時(shí)獲取內(nèi)容填入表單的示例
這篇文章主要介紹了vuejs+element UI點(diǎn)擊編輯表格某一行時(shí)獲取內(nèi)容填入表單的示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10詳解vue中使用vue-quill-editor富文本小結(jié)(圖片上傳)
這篇文章主要介紹了詳解vue中使用vue-quill-editor富文本小結(jié)(圖片上傳),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04vue-cli的build的文件夾下沒(méi)有dev-server.js文件配置mock數(shù)據(jù)的方法
這篇文章主要介紹了vue-cli的build的文件夾下沒(méi)有dev-server.js文件配置mock數(shù)據(jù)的方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04Vue引入并使用Element組件庫(kù)的兩種方式小結(jié)
本文主要介紹了Vue引入并使用Element組件庫(kù)的兩種方式小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01vue生成token保存在客戶端localStorage中的方法
本篇文章主要介紹了vue生成token保存在客戶端localStorage中的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10