※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。
最近寫我自己的後台開發框架,要弄一個多頁面標籤功能,之前有試過vue-element-admin的多頁面,以為很完美,就按它的思路重新寫了一個,但發現還是有問題的。
vue-element-admin它用的是在keep-alive組件上使用include屬性,綁定$store.state.tagsView.cachedViews,當點擊菜單時,往$store.state.tagsView.cachedViews添加頁面的name值,在標籤卡上點擊關閉后就從$store.state.tagsView.cachedViews裏面把緩存的name值刪除掉,這樣聽似乎沒什麼問題。但它無法很好的支持無限級別的子菜單的緩存。
目前vue-element-admin官方預覽地址的菜單結構大多是一級菜單分類,下面是二級子菜單。如下圖所示,它只能緩存二級子菜單,三級子菜單它緩存不了。為什麼會出現這個情況呢。因為嵌套router-view的問題。
按vue-element-admin的路由結構,它的一級菜單,其實對應的是一個layout組件,layout裏面有個router-view(稱它為一級router-view)它有用keep-alive包裹着,用來放二級菜單對應的頁面,所以對於二級菜單來說,它都是用同一個router-view。如果我需要創建三級菜單的話,那就需要在二級菜單目錄里創建一個包含router-view(稱它為二級router-view)的index.vue文件,用來放三級菜單對應的頁面,那麼你就會發現這個三級菜單的頁面怎麼也緩存不了。
因為只有一級router-view被keep-alive包裹起着緩存作用,下面的router-view它不緩存。當然我們也可以在二級的router-view也包一個keep-alive,也用include屬性,但你會發現也用不了,因為還要匹配name值,就是說二級router-view的文件也得寫上name值,寫上name值后你發現還是用不了,因為include數組裡面沒有這個二級router-view的name值,所以你還得在tabsView里的addView裏面做手腳,把路由所匹配到的所有路由的name值都添加到cachedViews里,然後還要在關閉時再進行處理。天啊。我想想都頭痛,理論是應該是可以實現的,但會增加了很多前端代碼量。
請注意!下面的方法也是有Bug的,請重點看下面的BUT開始部分
還好keep-alive還有另一個屬性exclude,我馬上就有思路了,而且非常簡潔,默認全部頁面進行緩存,所有的router-view都包一層keep-alive,只有在點擊標籤卡上的關閉按鈕時,往$store.state.sys.excludeViews添加關閉頁面的name值,下次打開后再從excludeViews裏面把頁面的name值刪除掉就行了,非常地簡單易懂,不過最底層的頁面,仍然需要寫上跟路由定義時完全匹配的name值。這一步我仍然想不到有什麼辦法可以省略掉。
為方便代碼,我寫了一個組件aliveRouterView組件,併合局註冊,這個組件用來代替router-view組件,如下面代碼所示,$store.state.sys.config.PAGE_TABS這個值是是否開戶多頁面標籤功能參數
<template>
<keep-alive :exclude="exclude">
<router-view />
</keep-alive>
</template>
<script>
export default {
computed: {
exclude() {
if (this.$store.state.sys.config.PAGE_TABS) {
return this.$store.state.sys.excludeViews;
} else {
return /.*/;
}
}
}
};
</script>
多頁面標籤組件viewTabs.vue,如下面代碼所示
<template>
<div class="__common-layout-tabView">
<el-scrollbar>
<div class="__tabs">
<div
class="__tab-item"
:class="{ '__is-active':item.name==$route.name }"
v-for="item in viewRouters"
:key="item.path"
@click="onClick(item)"
>
{{item.meta.title}}
<span
class="el-icon-close"
@click.stop="onClose(item)"
:style="viewRouters.length<=1?'width:0;':''"
></span>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script>
export default {
data() {
return {
viewRouters: []
};
},
watch: {
$route: {
handler(v) {
if (!this.viewRouters.some(item => item.name == v.name)) {
this.viewRouters.push(v);
}
},
immediate: true
}
},
methods: {
onClick(data) {
if (this.$route.fullPath != data.fullPath) {
this.$router.push(data.fullPath);
}
},
onClose(data) {
let index = this.viewRouters.indexOf(data);
if (index >= 0) {
this.viewRouters.splice(index, 1);
if (data.name == this.$route.name) {
this.$router.push(this.viewRouters[index < 1 ? 0 : index - 1].path);
}
this.$store.dispatch("excludeView", data.name);
}
}
}
};
</script>
<style lang="scss">
.__common-layout-tabView {
$c-tab-border-color: #dcdfe6;
position: relative;
&::before {
content: "";
border-bottom: 1px solid $c-tab-border-color;
position: absolute;
left: 0;
right: 0;
bottom: 2px;
height: 100%;
}
.__tabs {
display: flex;
.__tab-item {
white-space: nowrap;
padding: 8px 6px 8px 18px;
font-size: 12px;
border: 1px solid $c-tab-border-color;
border-left: none;
border-bottom: 0px;
line-height: 14px;
cursor: pointer;
transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
&:first-child {
border-left: 1px solid $c-tab-border-color;
border-top-left-radius: 2px;
margin-left: 10px;
}
&:last-child {
border-top-right-radius: 2px;
margin-right: 10px;
}
&:not(.__is-active):hover {
color: #409eff;
.el-icon-close {
width: 12px;
margin-right: 0px;
}
}
&.__is-active {
padding-right: 12px;
border-bottom: 1px solid #fff;
color: #409eff;
.el-icon-close {
width: 12px;
margin-right: 0px;
margin-left: 2px;
}
}
.el-icon-close {
width: 0px;
height: 12px;
overflow: hidden;
border-radius: 50%;
font-size: 12px;
margin-right: 12px;
transform-origin: 100% 50%;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
vertical-align: text-top;
&:hover {
background-color: #c0c4cc;
color: #fff;
}
}
}
}
}
</style>
貼上我的sys的store文件,後面我發現,我把頁面name添加到excludeViews后,在下一幀中再從excludeViews中把name刪除后,這樣也能有效果。如下面excludeView所示。這樣就更加簡潔。我只需在關閉標籤卡時處理一下就行了。
const sys = {
state: {
permissionRouters: [],//權限路由表
permissionMenus: [],//權限菜單列表
config: null, //系統配置
excludeViews: [] //用於多頁面選項卡
},
getters: {
},
mutations: {
SET_PERMISSION_ROUTERS(state, routers) {
state.permissionRouters = routers;
},
SET_PERMISSION_MENUS(state, menus) {
state.permissionMenus = menus;
},
SET_CONFIG(state, config) {
state.config = config;
},
ADD_EXCLUDE_VIEW(state, viewName) {
state.excludeViews.push(viewName);
},
DEL_EXCLUDE_VIEW(state, viewName) {
let index = state.excludeViews.indexOf(viewName);
if (index >= 0) {
state.excludeViews.splice(index, 1);
}
}
},
actions: {
//排除頁面
excludeView({ state, commit, dispatch }, viewName) {
if (!state.excludeViews.includes(viewName)) {
commit("ADD_EXCLUDE_VIEW", viewName);
Promise.resolve().then(() => {
commit("DEL_EXCLUDE_VIEW", viewName);
})
}
}
}
}
export default sys
效果如下圖所示,記得一點,就是得在你的頁面上填寫name值,需要跟定義路由時完全一致
BUT!!當我截完上面的動圖后,我就發現了問題了,而且是一個無法解決的問題,按我上面的方法,如果我點一下首頁,再點回原來的用戶管理,再關閉用戶管理,再打開用戶管理,你會發現緩存一直都在。
這是為什麼呢?究根詰底還是這個嵌套router-view的問題,不同的router-view的緩存是獨立的,首頁頁面是緩存在一級router-view下面,而用戶管理頁面是緩存在二級router-view下面,當我關閉用戶管理頁面后,只是往excludeViews添加了用戶管理頁面的name(sys.anme),所以只會刪除二級router-view下面name值為sys.user的頁面,二級router-view的name值為sys,它還緩存在一級router-view,所以導致用戶管理一直緩存着。
當然我也想過在關閉頁面時,把頁面父級的所有router-view的name值都添加到excludeViews裏面,這樣的話,也會出現問題,就是當我關閉用戶管理頁面后,同樣在name值為sys的二級router-view下面的頁面緩存都刪除掉了。
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。
當我測試了一晚上,我發現這真的是無解的,中間我也試過網上說的暴力刪除cache方法(方法介紹),也是因為這個嵌套router-view的問題導致失敗。
其實網上有人提出的解決方法是把框架改成只有一個一級router-view,一開始我覺得這是個下策,後面發現這也是唯一的方法了。
無奈,我確實不想扔棄這個多頁面標籤功能。那就改吧,其實改起來也不複雜,就是將菜單跟路由數組分為兩成數組,各自獨立。路由全部同級,均在layout布局組件的children裏面。
只使用一級router-view後面,這個多頁面標籤功能就非常好解決了,用include或exclude都可以,沒有什麼問題,但這兩種方法都得在頁面上寫name值,我是一個懶惰的程序員,總是寫這種跟業務無關係的name值顯得特別多餘。幸運的是,我之前在網上有找到一種暴力刪除緩存的方法,經過我的測試后,發現只有一個小問題(下面會提到),其它方面幾乎完美,而且跟include、exclude相比,還能完美支持同個頁面可以根據不同參數同時緩存的功能。(在vue-element-admin裏面也有說到include是沒法支持這種功能的,如下圖)
思想是這樣的,在store里創建一個openedPageRouters(已打開的頁面路由數組),我watch路由的變化,當打開一個新頁面時,往openedPageRouters裏面添加頁面路由,當我關閉頁面標籤時,到openedPageRouters裏面刪除對應的頁面路由,而上面提到的暴力刪除緩存,是在頁面的beforeRouterLeave事件中進行刪除中,所以我註冊一個全局mixin的beforeRouterLeave事件,檢測離開的頁面如果不存在於openedPageRouters數組裡面,那就進行緩存刪除。
思路很完美,當然裏面還有一個小問題,就是刪除不是當前激活的頁面,怎麼處理,因為beforeRouterLeave必須在要刪除頁面的生命周期才能觸發的,這個我用了點小手段,我先跳轉到要刪除的頁面,然後往openedPageRouters里刪除這個頁面路由,然後再跳回原來的頁面,這樣就能讓它觸發beforeRouterLeave了。哈哈,不過這個會導致一個小問題,就是地址欄的閃動一下,也就是上面提到的小問題。
下面是我的pageTabs.vue多頁面標籤組件的代碼
<template>
<div class="__common-layout-pageTabs">
<el-scrollbar>
<div class="__tabs">
<div
class="__tab-item"
v-for="item in $store.state.sys.openedPageRouters"
:class="{ '__is-active': item.meta.canMultipleOpen?item.fullPath==$route.fullPath:item.path==$route.path }"
:key="item.fullPath"
@click="onClick(item)"
>
{{item.meta.title}}
<span
class="el-icon-close"
@click.stop="onClose(item)"
:style="$store.state.sys.openedPageRouters.length<=1?'width:0;':''"
></span>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script>
export default {
watch: {
$route: {
handler(v) {
this.$store.dispatch("openPage", v);
},
immediate: true
}
},
methods: {
//點擊頁面標籤卡時
onClick(data) {
if (this.$route.fullPath != data.fullPath) {
this.$router.push(data.fullPath);
}
},
//關閉頁面標籤時
onClose(route) {
if (route.fullPath == this.$route.fullPath) {
let index = this.$store.state.sys.openedPageRouters.indexOf(route);
this.$store.dispatch("closePage", route);
//刪除頁面后,跳轉到上一頁面
this.$router.push(
this.$store.state.sys.openedPageRouters[index < 1 ? 0 : index - 1]
.path
);
} else {
let lastPath = this.$route.fullPath;
//先跳轉到要刪除的頁面,再刪除頁面路由,再跳轉回來原來的頁面
this.$router.replace(route).then(() => {
this.$store.dispatch("closePage", route);
this.$router.replace(lastPath);
});
}
}
}
};
</script>
<style lang="scss">
.__common-layout-pageTabs {
$c-tab-border-color: #dcdfe6;
position: relative;
&::before {
content: "";
border-bottom: 1px solid $c-tab-border-color;
position: absolute;
left: 0;
right: 0;
bottom: 2px;
height: 100%;
}
.__tabs {
display: flex;
.__tab-item {
white-space: nowrap;
padding: 8px 6px 8px 18px;
font-size: 12px;
border: 1px solid $c-tab-border-color;
border-left: none;
border-bottom: 0px;
line-height: 14px;
cursor: pointer;
transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
&:first-child {
border-left: 1px solid $c-tab-border-color;
border-top-left-radius: 2px;
margin-left: 10px;
}
&:last-child {
border-top-right-radius: 2px;
margin-right: 10px;
}
&:not(.__is-active):hover {
color: #409eff;
.el-icon-close {
width: 12px;
margin-right: 0px;
}
}
&.__is-active {
padding-right: 12px;
border-bottom: 1px solid #fff;
color: #409eff;
.el-icon-close {
width: 12px;
margin-right: 0px;
margin-left: 2px;
}
}
.el-icon-close {
width: 0px;
height: 12px;
overflow: hidden;
border-radius: 50%;
font-size: 12px;
margin-right: 12px;
transform-origin: 100% 50%;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
vertical-align: text-top;
&:hover {
background-color: #c0c4cc;
color: #fff;
}
}
}
}
}
</style>
以下是store代碼
const sys = {
state: {
menus: [],//
permissionRouters: [],//權限路由表
permissionMenus: [],//權限菜單列表
config: null, //系統配置
openedPageRouters: [] //已打開原頁面路由
},
getters: {
},
mutations: {
SET_PERMISSION_ROUTERS(state, routers) {
state.permissionRouters = routers;
},
SET_PERMISSION_MENUS(state, menus) {
state.permissionMenus = menus;
},
SET_MENUS(state, menus) {
state.menus = menus;
},
SET_CONFIG(state, config) {
state.config = config;
},
//添加頁面路由
ADD_PAGE_ROUTER(state, route) {
state.openedPageRouters.push(route);
},
//刪除頁面路由
DEL_PAGE_ROUTER(state, route) {
let index = state.openedPageRouters.indexOf(route);
if (index >= 0) {
state.openedPageRouters.splice(index, 1);
}
},
//替換頁面路由
REPLACE_PAGE_ROUTER(state, route) {
for (let key in state.openedPageRouters) {
if (state.openedPageRouters[key].path == route.path) {
state.openedPageRouters.splice(key, 1, route)
break;
}
}
}
},
actions: {
//打開頁面
openPage({ state, commit }, route) {
let isExist = state.openedPageRouters.some(
item => item.fullPath == route.fullPath
);
if (!isExist) {
//判斷頁面是否支持不同參數多開頁面功能,如果不支持且已存在path值一樣的頁面路由,那就替換它
if (route.meta.canMultipleOpen || !state.openedPageRouters.some(
item => item.path == route.path
)) {
commit("ADD_PAGE_ROUTER", route);
} else {
commit("REPLACE_PAGE_ROUTER", route);
}
}
},
//關閉頁面
closePage({ state, commit }, route) {
commit("DEL_PAGE_ROUTER", route);
}
}
}
export default sys
以下是暴力刪除頁面緩存的代碼,我寫成了一個全局的mixin
import Vue from 'vue'
Vue.mixin({
beforeRouteLeave(to, from, next) {
//限制只有在我寫的那個父類里才可能會用這個緩存刪除功能
if (!this.$parent || this.$parent.$el.className != "el-main __common-layout-main" || !this.$store.state.sys.config.PAGE_TABS) {
next();
return;
}
let isExist = this.$store.state.sys.openedPageRouters.some(item => item.fullPath == from.fullPath)
if (!isExist) {
let tag = this.$vnode.tag;
let cache = this.$vnode.parent.componentInstance.cache;
let keys = this.$vnode.parent.componentInstance.keys;
let key;
for (let k in cache) {
if (cache[k].tag == tag) {
key = k;
break;
}
}
if (key) {
if (cache[key] != null) {
delete cache[key];
let index = keys.indexOf(key);
if (index > -1) {
keys.splice(index, 1);
}
}
}
}
next();
}
})
然後router-view這樣使用,根據我的配置$store.state.sys.config.PAGE_TABS(是否啟用多頁面標籤)進行判斷 ,對了,我相信有不少人肯定會想到,路由不嵌套了,沒有matched數組了,怎麼弄麵包屑,可以看我下面代碼的處理,$store.state.sys.permissionMenus這個數組是我從後台傳過來的,是一個根據當前用戶的權限獲取到的所有有權限訪問的菜單數組,都是一級數組,沒有嵌套關係,我的菜單數組跟路由都是根據這個permissionMenus進行構建的。而我的麵包屑數組就是從這個數組遞歸出來的。
<template>
<el-main class="__common-layout-main">
<page-tabs class="c-mg-t-10p" v-if="$store.state.sys.config.PAGE_TABS" />
<div class="c-pd-20p">
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="m in breadcrumbItems" :key="m.id">{{m.name}}</el-breadcrumb-item>
</el-breadcrumb>
<div class="c-h-15p"></div>
<keep-alive v-if="$store.state.sys.config.PAGE_TABS">
<router-view :key="$route.fullPath" />
</keep-alive>
<router-view v-else />
</div>
</el-main>
</template>
<script>
import pageTabs from "./pageTabs";
export default {
components: { pageTabs },
data() {
return {
viewNames: ["role"]
};
},
computed: {
breadcrumbItems() {
let items = [];
let buildItems = id => {
let b = this.$store.state.sys.permissionMenus.find(
item => item.id == id
);
if (b) {
items.unshift(b);
if (b.parentId) {
buildItems(b.parentId);
}
}
};
buildItems(this.$route.meta.id);
return items;
}
}
};
</script>
<style lang="scss">
$c-tab-border-color: #dcdfe6;
.__common-layout-main.el-main {
padding: 0px;
overflow: unset;
.el-breadcrumb {
font-size: 12px;
}
}
</style>
演示一個最終效果,哎,弄了我整整两天時間,不過我改成不嵌套路由后,發現代碼量也少了很多,也是因禍得福啊。這更符合我的Less框架的理念了。哈哈哈!
對了,我之前有說到個小問題,大家可以仔細看一下,下圖的地址欄,當我關閉非當前激活的頁面標籤時,你會發現地址欄會閃現一下。好吧,下面這個動圖還不太明顯。
大家可以到我的LessAdmin框架預覽地址測試下,不要亂改菜單數據哦,會導致打不開的
http://test.caijt.com:9001
用戶:superadmin
密碼:admin
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單