vue+elementui+vuex+sessionStorage實現(xiàn)歷史標簽菜單的示例代碼
一般是有左側(cè)菜單后,然后要在頁面上部分添加歷史標簽菜單需求。
借鑒其他項目,以及網(wǎng)上功能加以組合調(diào)整實現(xiàn)
按照標簽實現(xiàn)方式步驟來(大致思路):
1,寫一個tagNav標簽組件
2,在路由文件上每個路由組件都添加meta屬性 meta:{title:'組件中文名'}
3,在store的mutation.js文件中寫標簽的添加/刪除方法以及在方法中更新sessionStorage數(shù)據(jù)
4,在主頁面上添加組件以及router-view外層添加keep-alive組件,我這邊是main.vue為登錄后主要的主頁面,其他菜單頁面都基于該頁面的路由上
5,寫一個mixins文件:beforeRouteLeave回調(diào),因為貌似只在這回調(diào)中能找到子頁面的緩存對象。在main.js中引入該文件并加入到vue.minxin()
全局方法中,節(jié)省了在每個子頁面都寫改回調(diào)。
6,左側(cè)菜單也要添加路由監(jiān)聽,用于點標簽菜單時左側(cè)菜單能定位選中到對應的菜單選項
7,如果點標簽菜單是路由重定向菜單,則需要在觸發(fā)重定向的路由頁面添加路由監(jiān)聽獲取meta.title的路由屬性,然后在頁面的created回調(diào)中循環(huán)存放標簽的store數(shù)組,并把meta.title設(shè)置為當前重定向的標簽名
開始代碼說明
寫一個tagNav組件
<style lang="less" scoped> @import "./base.less"; .tags-nav { display: flex; align-items: stretch; height: 40px; padding: 2px 0; background-color: @background-color; a { margin-right: 1px; width: 24px; } a:hover { color: @light-theme-color; } a:first-of-type { margin-right: 4px; } a, .dropdown-btn { display: inline-block; width:30px; height: 36px; color: @title-color; background-color: @white; text-align: center; line-height: 36px; position: relative; z-index: 10; } .tags-wrapper { flex: 1 1 auto; position: relative; .tags-wrapper-scroll { position: absolute; top: 0px; left: 0; z-index: 5; height: 36px; overflow: visible; white-space: nowrap; transition: all .3s ease-in-out; .tag { flex-shrink: 0; cursor: pointer; } } } } </style> <template> <div class="tags-nav"> <a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" @click="handleScroll('left')"> <icon name="angle-left"></icon> </a> <div class="tags-wrapper" ref="tagsWrapper"> <div class="tags-wrapper-scroll" ref="tagsWrapperScroll" :style="{ left: leftOffset + 'px' }"> <transition-group name="slide-fade"> <el-tag class="tag slide-fade-item" ref="tagsPageOpened" v-for="(tag, index) in pageOpenedList" :key="'tag_' + index" :type="tag.selected ? '': 'info'" :closable="tag.name!='basicDevice'" :id="tag.name" effect="dark" :text="tag.name" @close="closeTag(index, $event, tag.name)" @click="tagSelected(index)" >{{tag.title}}</el-tag> </transition-group> </div> </div> <a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" @click="handleScroll('right')"> <icon name="angle-right"></icon> </a> <!-- <el-dropdown class="dropdown-btn" @command="closeTags"> <span class="el-dropdown-link"> <icon name="angle-down"></icon> </span> <el-dropdown-menu slot="dropdown"> <el-dropdown-item command="closeOthers">關(guān)閉其他</el-dropdown-item> <el-dropdown-item command="closeAll">關(guān)閉所有</el-dropdown-item> </el-dropdown-menu> </el-dropdown> --> <!-- <Dropdown placement="bottom-end" @on-click="closeTags"> <a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" style="margin-right: 0;"> <icon name="angle-down"></icon> </a> <DropdownMenu slot="list"> <DropdownItem name="closeOthers">關(guān)閉其他</DropdownItem> <DropdownItem name="closeAll">關(guān)閉所有</DropdownItem> </DropdownMenu> </Dropdown> --> </div> </template> <script> export default { data () { return { currentPageName: this.$route.name, leftOffset: 0 } }, props: { pageOpenedList: { type: Array } }, methods: { closeTags (action) { this.$emit('closeTags', action) if (action === 'closeOthers') { this.leftOffset = 0 } }, closeTag (index, event, name) { // 移除單個tag,且首頁的tag無法移除 if (index !== 0) { this.$emit('closeTags', index,name) } if (this.currentPageName !== name) { this.leftOffset = Math.min(0, this.leftOffset + event.target.parentNode.offsetWidth) } }, tagSelected (index) { this.$emit('tagSelected', index) }, checkTagIsVisible (tag) { let visible = { isVisible: false, position: 'left' } const leftDiffValue = tag.offsetLeft + this.leftOffset if (leftDiffValue < 0) { return visible } const rightDiffValue = this.$refs.tagsWrapper.offsetWidth - this.leftOffset - tag.offsetWidth - tag.offsetLeft if (leftDiffValue >= 0 && rightDiffValue >= 0) { visible.isVisible = true } else { visible.position = 'right' } return visible }, handleScroll (direaction) { // 獲取在可視區(qū)域臨界的tag let criticalTag = this.getCriticalTag(direaction) switch (direaction) { case 'left': this.leftOffset = Math.min(this.$refs.tagsWrapper.offsetWidth - criticalTag.$el.offsetLeft, 0) break case 'right': const diffValue1 = -(criticalTag.$el.offsetLeft + criticalTag.$el.clientWidth) const diffvalue2 = -(this.$refs.tagsWrapperScroll.offsetWidth - this.$refs.tagsWrapper.offsetWidth) this.leftOffset = Math.max(diffValue1, diffvalue2) break default: break } }, getCriticalTag (direaction) { let criticalTag const refsTagList = this.$refs.tagsPageOpened for (let tag of refsTagList) { // 檢查tag是否在可視區(qū) if (this.checkTagIsVisible(tag.$el).isVisible) { criticalTag = tag if (direaction === 'left') { break } } } return criticalTag }, setTagsWrapperScrollPosition (tag) { const visible = this.checkTagIsVisible(tag) if (!visible.isVisible && visible.position === 'left') { // 標簽位于可視區(qū)域的左側(cè) this.leftOffset = -tag.offsetLeft } else { // 標簽位于可視區(qū)域的右側(cè) or 可視區(qū)域 this.leftOffset = Math.min(0, -(tag.offsetWidth + tag.offsetLeft - this.$refs.tagsWrapper.offsetWidth + 4)) } } }, mounted () { // 初始化當前打開頁面的標簽位置 const refsTag = this.$refs.tagsPageOpened setTimeout(() => { for (const tag of refsTag) { if (tag.text === this.$route.name) { const tagNode = tag.$el this.setTagsWrapperScrollPosition(tagNode) break } } }, 1) }, watch: { $route (to) { this.currentPageName = to.name this.$nextTick(() => { const refsTag = this.$refs.tagsPageOpened for (const tag of refsTag) { if (tag.text === this.$route.name) { const tagNode = tag.$el this.setTagsWrapperScrollPosition(tagNode) break } } }) } } } </script>
以及在同層目錄下的less文件
// color @theme1-color: #515a6e; @theme-color: #2d8cf0; @light-theme-color: #5cadff; @dark-theme-color: #2b85e4; @info-color: #2db7f5; @success-color: #19be6b; @warning-color: #ff9900; @error-color: #ed4014; @title-color: #17233d; @content-color: #515a6e; @sub-color: #808695; @disabled-color: #c5c8ce; @border-color: #dcdee2; @divider-color: #e8eaec; @background-color: #f8f8f9; @white: white; // 間距 @padding: 16px; // 默認樣式 * { box-sizing: border-box; } a { color: @theme-color; } a:hover { color: @light-theme-color; } .dark-a { color: @title-color; } // 清除float .clear-float::after { display: block; clear: both; content: ""; visibility: hidden; height: 0; } // 動畫 .slide-fade-item { transition: all 0.1s ease-in-out; display: inline-block; } .slide-fade-enter, .slide-fade-leave-to /* .list-complete-leave-active for below version 2.1.8 */ { opacity: 0; transform: translateX(-10px); } // 滾動條樣式 .menu-scrollbar::-webkit-scrollbar, .common-scrollbar::-webkit-scrollbar { /*滾動條整體樣式*/ width: 11px; /*高寬分別對應橫豎滾動條的尺寸*/ height: 1px; } // 滾動條樣式1 .menu-scrollbar::-webkit-scrollbar-thumb { /*滾動條里面小方塊*/ border-radius: 2px; box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); background: @sub-color; } // 滾動條樣式2 .common-scrollbar::-webkit-scrollbar-thumb { /*滾動條里面小方塊*/ border-radius: 2px; box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); background: @border-color; }
-----------說明:由于這關(guān)閉所有跟關(guān)閉其他的功能只是單純清除tagNav的標簽并沒關(guān)系到路由,所以獲取不到清除的頁面路由離開事件,只能先關(guān)閉這兩功能------------
在路由文件route.js中為每個路由添加meta屬性
{ path: 'auditManage', name: 'auditManage', meta:{title:'審計管理'}, component: function (resolve) { require(['../page/sysConfig/audit/auditManageMain.vue'], resolve); }, redirect: '/main/auditManage/trendStatistics', children: [{ path: 'trendStatistics', name: 'trendStatistics', meta:{title:'趨勢審計'}, component: function (resolve) { require(['../page/sysConfig/audit/auditTrendStatisticsList.vue'], resolve); } }, { path: 'search', name: 'search', meta:{title:'審計查詢'}, component: function (resolve) { require(['../page/sysConfig/audit/auditSearchList.vue'], resolve); } },
------說明:這是路由片段包含設(shè)置的meta屬性方式內(nèi)容,以及路由重定向-------
在store的mutation.js中寫標簽的添加/刪除以及更新sessionStorage方法
[setPageOpenedList](state,params = null){ // 設(shè)置前先讀取本地保存的打開列表數(shù)據(jù) state.pageOpenedList = sessionStorage.pageOpenedList ? JSON.parse(sessionStorage.pageOpenedList) : [{ title: '基礎(chǔ)設(shè)施', name: 'basicDevice', selected: true }] if (!params) { return } if (params.index === -1) { // 新打開一個頁面 state.pageOpenedList.push({ title: params.route.meta.title, name: params.route.name, selected: false }) params.index = state.pageOpenedList.length - 1 } // 更新selected值 for (let i = 0; i < state.pageOpenedList.length; i++) { if (params.index === i) { state.pageOpenedList[i].selected = true } else { state.pageOpenedList[i].selected = false } } // 更新下本地新的打開頁面列表數(shù)據(jù) sessionStorage.pageOpenedList = JSON.stringify(state.pageOpenedList) }, // 移除PageOpenedList [removePageOpenedList] (state, params = null) { if (!params) { return } if (typeof params.action === 'number') { state.pageOpenedList.splice(params.action, 1) } else { //進這里是已經(jīng)選擇刪除所有tab,賦值一個初始選中的tab state.pageOpenedList = [{ title: '基礎(chǔ)設(shè)施', name: 'basicDevice', selected: true }] //如果是刪除其他的則再加上當前路由下的tab if (params.action === 'closeOthers' && params.route.name !== 'basicDevice') { state.pageOpenedList[0].selected = false state.pageOpenedList.push({ title: params.route.meta.title, name: params.route.name, selected: true }) } } // 更新下本地新的打開頁面列表數(shù)據(jù) sessionStorage.pageOpenedList = JSON.stringify(state.pageOpenedList) },
------說明:由于有的項目中store寫法不太一樣,這里的[setPageOpenedList]
以及[removePageOpenedList]
是mutation-type.js中定義的常量---------
在主頁面main.vue中添加標簽組件、keep-alive組件、組件的選中/刪除方法,監(jiān)聽路由的變化,計算存放的標簽list
<div class="a-tag"> <tag-nav :pageOpenedList="pageOpenedList" ref="tagNavRef" @closeTags="closeTags" @tagSelected="tagSelected"></tag-nav> </div> <div class="a-product"> <div class="loading" style="height:100%"> <keep-alive :max="5"> <router-view></router-view> </keep-alive> </div> </div>
// 導航標簽方法 closeTags (action,elId) { let isRemoveSelected; if (typeof action === 'number') { //移除單個 let elEvent = new Event('click'); document.getElementById(elId).dispatchEvent(elEvent); //移除不管是不是當前的標簽都默認設(shè)置為是當前的標簽 for (let i = 0; i < this.$store.state.pageOpenedList.length; i++) { if (action === i) { this.$store.state.pageOpenedList[i].selected = true } else { this.$store.state.pageOpenedList[i].selected = false } } //并且是當前的標簽頁 isRemoveSelected = this.$store.state.pageOpenedList[action].selected } this.$store.commit('removePageOpenedList', { route: this.$route, action }) if (isRemoveSelected) { // 移除單個tag,導航到最后一個tag的頁面 this.$router.push({ name: this.$store.state.pageOpenedList[this.$store.state.pageOpenedList.length - 1].name }) } else if (action === 'closeAll') { this.$router.push('/main/basicDevice'); } }, tagSelected (index) { if (this.$store.state.pageOpenedList[index].name !== this.$route.name) { this.$router.push({ name: this.$store.state.pageOpenedList[index].name }) } },
computed: { pageOpenedList () { return this.$store.getters.getPageOpenedList }, },
watch: { $route (to) { // 路由變化,更新PageOpenedList let index = this.$util.indexOfCurrentPageOpened(to.name, this.$store.state.pageOpenedList) this.$store.commit('setPageOpenedList', { route: to, index }) }, }
// 定位新打開的頁面在pageOpenedList中位置 indexOfCurrentPageOpened(name, pageOpenedList) { for (let index = 0; index < pageOpenedList.length; index++) { if (pageOpenedList[index].name === name) { return index } } return -1 },
------說明:組件的導入就不貼了,緩存限制最多為5個標簽,怕開太多導致瀏覽器內(nèi)存爆炸卡死。-------
寫一個minxins文件:路由離開回調(diào),并在main.js中全局混入
/** * 用于歷史標簽菜單 */ export default { beforeRouteLeave(to, from, next) { console.log("mixins-beforeRouteLeave:",from); let flag = true this.$store.state.pageOpenedList.forEach(e => { // pageOpenedList存儲打開的tabs的組件路由 if(from.name == e.name) { flag = false } }) if(flag && this.$vnode.parent && this.$vnode.parent.componentInstance.cache) { // let key = this.$vnode.key // 當前關(guān)閉的組件名 var key = (this.$vnode.key == null || this.$vnode.key == undefined) ? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '') : this.$vnode.key; let cache = this.$vnode.parent.componentInstance.cache // 緩存的組件 let keys = this.$vnode.parent.componentInstance.keys // 緩存的組件名 if(cache[key] != null) { delete cache[key] let index = keys.indexOf(key) if(index > -1) { keys.splice(index, 1) } } } next() } }
//引入混合方法 import beforeLeave from './mixins/beforeLeave' Vue.mixin(beforeLeave)
------說明:這里主要就是為在每個離開的路由頁面都回調(diào)這個離開方法,并且判斷當前離開的路由是切換到其他標簽菜單還是關(guān)閉當前菜單標簽,如果是關(guān)閉則刪除對應的緩存---------
左側(cè)菜單也要添加路由監(jiān)聽,以便切換標簽菜單時左側(cè)菜單能正確的跳轉(zhuǎn)并選中標簽
handleSelect(index,indexPath){ this.active = index; },
watch:{ $route (to) { console.log("accordion=============",to,to.path); //進if判斷說明是被重定向的地址,則使用重定向父級菜單路徑去跳轉(zhuǎn),讓父級頁面自己去重定向 if(to.matched && to.matched[to.matched.length-1].parent.redirect != undefined){ this.handleSelect(to.matched[to.matched.length-1].parent.path,null); }else { this.handleSelect(to.path,null); } } },
-----說明:第一個方法是el-menu選擇菜單時觸發(fā)的方法,watch路由時獲取matched屬性值去判斷------
如果切換的標簽是重定向的路由菜單則需要在重定向頁面初始化時獲取存放標簽數(shù)組的title設(shè)置對應的radio標簽(我這邊項目的重定向頁面基本都是用radio去切換),如果是再切換后的就是緩存的了,那就要從路由上獲取title
<div class="tabsBox"> <el-radio-group class="radioStyle" v-for="item in menus" :key="item.route" v-model="activeName" @change="checkItem(item.route)"> <el-radio-button :label="item.text" @blur.native="goPath(item.route)"></el-radio-button> </el-radio-group> </div>
data(){ return { activeName:'參與人', menus:[ {route:"partyManageReport",text:"參與人"}, {route:"staffManageReport",text:"員工"}, {route:"partyAccountManageReport",text:"主賬號"}, {route:"partyAccountGroupManageReport",text:"主賬號組"}, {route:"partySubAccountManageReport",text:"從賬號(web資產(chǎn))"}, {route:"partySubAccountManageD",text:"從賬號(基礎(chǔ)設(shè)施)"} ] } }, watch:{ $route (to){ this.activeName = to.meta.title; } }, created(){ this.$store.state.pageOpenedList.forEach((item)=>{ if(item.selected){ this.activeName = item.title; } }) }
總結(jié):由于在關(guān)閉標簽方法中有添加點擊事件,把關(guān)閉是其他標簽時設(shè)置為當前標簽并關(guān)閉,這樣才能獲取到關(guān)閉標簽對應的路由離開回調(diào)去清除緩存。但這樣就會導致關(guān)閉其他標簽后當前選中的標簽頁會跳到最后一個標簽去。如果要求不是很高,這樣也能勉強接受吧。不過感覺不是那么好。有沒好的方式關(guān)閉其他標簽時既能清除對應的緩存當前選中的標簽tab又不會跳轉(zhuǎn)到其他的標簽選項上…求優(yōu)化思路。
另關(guān)閉所有跟關(guān)閉其他選項,還不知道怎么去清除被關(guān)閉的標簽緩存,如上面所說獲取不到關(guān)閉時觸發(fā)路由離開方法,只是單純重新設(shè)置標簽數(shù)組而已。
第一個是基礎(chǔ)標簽,所以把關(guān)閉按鈕去了。
到此這篇關(guān)于vue+elementui+vuex+sessionStorage實現(xiàn)歷史標簽菜單的示例代碼的文章就介紹到這了,更多相關(guān)vue 歷史標簽菜單內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue element實現(xiàn)表格增加刪除修改數(shù)據(jù)
這篇文章主要為大家詳細介紹了vue element實現(xiàn)表格增加刪除修改數(shù)據(jù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05vue3?父控件遠程獲取數(shù)據(jù)在子組件上顯示不出來的解決方案
這篇文章主要介紹了vue3?父控件遠程獲取數(shù)據(jù),在子組件上顯示不出來,本文給大家分享兩種解決方案幫助大家解決這個問題,需要的朋友可以參考下2023-08-08vue?el-switch初始值(默認值)不能正確顯示狀態(tài)問題及解決
這篇文章主要介紹了vue?el-switch初始值(默認值)不能正確顯示狀態(tài)問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10