vue+elementui+vuex+sessionStorage實現歷史標籤選單的示例程式碼
一般是有左側選單後,然後要在頁面上部分新增歷史標籤選單需求。
借鑑其他專案,以及網上功能加以組合調整實現
按照標籤實現方式步驟來(大致思路):
1,寫一個tagNav標籤元件
2,在路由檔案上每個路由元件都新增meta屬性 meta:{title:'元件中文名'}
3,在store的mutation.檔案中寫標籤的新增/刪除方法以及在方法中更新sessionStorage資料
4,在主頁面上新增元件以及router-view外層新增keep-alive元件,我這邊是main.為登入後主要的主頁面,其他選單頁面都基於該頁面的路由上
5,寫一個mixins檔案:beforeRouteLeave回撥,因為貌似只在這回調中能找到子頁面的快取物件。在main.js中引入該檔案並加入到vue.minxin()
6,左側選單也要新增路由監聽,用於點標籤選單時左側選單能定位選中到對應的選單選項
7,如果點標籤選單是路由重定向選單,則需要在觸發重定向的路由頁面新增路由監聽獲取meta.title的路由屬性,然後在頁面的created回撥中迴圈存放標籤的store陣列,並把meta.title設定為當前重定向的標籤名
開始程式碼說明
寫一個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=":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 + 'pxIsgym' }"> <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="script:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" @click="handleScroll('right')"> <icon name="angle-right">http://www.cppcns.com</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">關閉其他</el-dropdown-item> <el-dropdown-item command="closeAll">關閉所有</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">關閉其他</DropdownItem> <DropdownItem name="closeAll">關閉所有</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) { // 獲取在可視區域臨界的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是否在可視區 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') { // 標籤位於可視區域的左側 this.leftOffset = -tag.offsetLeft } else { // 標籤位於可視區域的右側 or 可視區域 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.2); background: @sub-color; } // 滾動條樣式2 .common-scrollbar::-webkit-scrollbar-thumb { /*滾動條裡面小方塊*/ border-radius: 2px; box-shadow: inset 0 0 5px rgba(0,0.2); background: @border-color; }
-----------說明:由於這關閉所有跟關閉其他的功能只是單純清除tagNav的標籤並沒關係到路由,所以獲取不到清除的頁面路由離開事件,只能先關閉這兩功能------------
在路由檔案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'],
------說明:這是路由片段包含設定的meta屬性方式內容,以及路由重定向-------
在store的mutation.js中寫標籤的新增/刪除以及更新sessionStorage方法
[setPageOpenedList](state,params = null){ // 設定前先讀取本地儲存的開啟列表資料 state.pageOpenedList = sessionStorage.pageOpenedList ? JSON.parse(sessionStorage.pageOpenedList) : [{ title: '基礎設施',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 } } // 更新下本地新的開啟頁面列表資料 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 { //進這裡是已經選擇刪除所有tab,賦值一個初始選中的tab state.pageOpenedList = [{ title: '基礎設施',selected: true }] //如果是刪除其他的則再加上當前路由下的tab if (params.action === 'closeOthers' && params.route.name !== 'basicDevice') { state.pageOpenedList[0].selected = false state.pageOpenedList.push({ title: params.route.meta.title,selected: true }) } } // 更新下本地新的開啟頁面列表資料 sessionStorage.pageOpenedList = JSON.stringify(state.pageOpenedList) },
------說明:由於有的專案中store寫法不太一樣,這裡的[setPageOpenedList]
以及[removePageOpenedList]
是mutation-type.js中定義的常量---------
在主頁面main.vue中新增標籤元件、keep-alive元件、元件的選中/刪除方法,監聽路由的變化,計算存放的標籤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); //移除不管是不是當前的標籤都預設設定為是當前的標籤 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個標籤,怕開太多導致瀏覽器記憶體爆炸卡死。-------
寫一個minxins檔案:路由離開回調,並在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 // 當前關閉的元件名 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)
------說明:這裡主要就是為在每個離開的路由頁面都回調這個離開方法,並且判斷當前離開的路由是切換到其他標籤選單還是關閉當前選單標籤,如果是關閉則刪除對應的快取---------
左側選單也要新增路由監聽,以便切換標籤選單時左側選單能正確的跳轉並選中標籤
handleSelect(index,indexPath){ thhttp://www.cppcns.comis.active = index; },
watch:{ $route (to) { console.log("accordion=============",to,to.path); //進if判斷說明是被重定向的地址,則使用重定向父級選單路徑去跳轉,讓父級頁面自己去重定向 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選擇選單時觸發的方法,watch路由時獲取matched屬性值去判斷------
如果切換的標籤是重定向的路由選單則需要在重定向頁面初始化時獲取存放標籤陣列的title設定對應的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:'參與人',menushttp://www.cppcns.com:[
{route:"partyManageReport",text:"參與人"},{route:"staffManageReport",text:"員工"},{route:"partyAccountManageReport",text:"主賬號"},{route:"partyAccountGroupManageReport",text:"主賬號組"},{route:"partySubAccountManageReport",text:"從賬號(web資產)"},{route:"partySubAccountManageD",text:"從賬號(基礎設施)"}
]
}
},watch:{
$route (to){
this.activeName = to.meta.title;
}
},created(){
this.$store.state.pageOpenedList.forEach((item)=>{
if(item.selected){
this.activeName = item.title;
}
})
}
總結:由於在關閉標籤方法中有新增點選事件,把關閉是其他標籤時設定為當前標籤並關閉,這樣才能獲取到關閉標籤對應的路由離開回調去清除快取。但這樣就會導致關閉其他標籤後當前選中的標籤頁會跳到最後一個標籤去。如果要求不是很高,這樣也能勉強接受吧。不過感覺不是那麼好。有沒好的方式關閉其他標籤時既能清除對應的快取當前選中的標籤tab又不會跳轉到其他的標籤選項上…求優化思路。
另關閉所有跟關閉其他選項,還不知道怎麼去清除被關閉的標籤快取,如上面所說獲取不到關閉時觸發路由離開方法,只是單純重新設定標籤陣列而已。
第一個是基礎標籤,所以把關閉按鈕去了。
到此這篇關於vue+elementui+vuex+sessionStorage實現歷史標籤選單的示例程式碼的文章就介紹到這了,更多相關vue 歷史標籤選單內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!