基於iview的router常用控制方式
1 iview的router控制需求
最近在使用iview框架寫專案,遇到了一些路由控制上的問題,解決過程中也有一些心得,故在此記錄下來.
每個專案在開發時,對於類似tags(標籤頁)的控制需求都不盡相同,故以下先列出本文所述專案對標籤頁的控制要求(如有不同需求,本文當也可提供一些思路):
- 對於同名(name)的路由標籤頁,不能開啟多個.譬如說從商品列表中開啟商品展示標籤頁,如果已經有在開啟的商品編輯頁面,則替換之.新開啟的,未儲存,已儲存的標籤頁,同時只能存在一個(即不同params相同name的route只能有一個);
- 替換掉一個新的頁面時,通過切換的方式切換回來(先切到其他標籤頁再切換回來),仍是原來頁面的內容(即實際記錄的params在替換後應變化).類似的情況,還應包含單據從未儲存到已儲存,以及儲存並新增功能;
2 基於vue的router控制
iview是基於vue的框架,故vue本身自帶的router控制方法是必然可行的.
vue變更路由的常用方式參考以下(該方法在官方api中有更詳細的介紹):
//變更當前路由(有歷史記錄,建議使用此方式)
this.$router.push({
name:'routerName',
params:routerParam
})
//變更當前路由(無歷史記錄)
this.$router.replace({
name:'routerName',
routerParam
})
官方路由變更確實可以正常開啟標籤頁,但在實現1中所提到的各種需求的時候,就有些不滿足需求了.為此,需要參考3中,如何基於iview的outer控制.
3 基於iview的router控制
iview在控制路由的時候,使用vuex中的app.js來記錄標籤頁路由資訊,如果對vuex還是很瞭解的話,可以通過這篇博文來先打一下基礎.
3.1 如何實現需求1.1
想要實現不同params相同name的route在iview中只能有一個,關鍵是改變iview對路由相等的判斷方法,即'/src/libs/util.js'裡的routeEqual方法:
/** * @description 根據name/params/query判斷兩個路由物件是否相等 * @param {*} route1 路由物件 * @param {*} route2 路由物件 */ export const routeEqual = (route1, route2) => { return route1.name === route2.name // 此處改變相同路由的判斷方式,改為name相同即認為相同 // const params1 = route1.params || {} // const params2 = route2.params || {} // const query1 = route1.query || {} // const query2 = route2.query || {} // return (route1.name === route2.name) && objEqual(params1, params2) && objEqual(query1, query2) }
這裡稍微解釋下(如果不關注原因,可以直接看3.2).當改變路由時,'src\components\main\main.vue'作為近乎頂層的元件控制著近乎所有的全域性邏輯,其中就有對路由的監控:
...
<side-menu
accordion
ref="sideMenu"
:active-name="$route.name"
:collapsed="collapsed"
@on-select="turnToPage"
:menu-list="menuList"
>
...
//此方法隸屬於methods,用以監控side-menu的選擇事件,即平時從左側選單開啟標籤頁的邏輯
turnToPage (route) {
let { name, params, query } = {}
if (typeof route === 'string') name = route
else {
name = route.name
params = route.params
query = route.query
}
if (name.indexOf('isTurnByHref_') > -1) {
window.open(name.split('_')[1])
return
}
this.$router.push({
name,
params,
query
})
},
...
watch: {
// 檢測route的變化
$route (newRoute) {
const { name, query, params, meta } = newRoute
this.addTag({
route: { name, query, params, meta },
type: 'push'
})
this.setBreadCrumb(newRoute)
this.setTagNavList(getNewTagList(this.tagNavList, newRoute))
this.$refs.sideMenu.updateOpenName(newRoute.name)
}
},
...
從以上程式碼可推測出,main.vue通過turnToPage方法實現開啟標籤頁的邏輯,但方法內部並沒有體現便籤頁顯示效果變化(包含內部資料變化,以下同)的邏輯,這是由於顯示效果變化的邏輯,由對$router的監控實現.
這樣,不止從左側選單開啟新標籤頁可以實現顯示變化效果,其他只要使用vue的原版push等方法改變router的方法,均可監測到.
逐步檢視下各個方法,其中影響當前標籤頁顯示效果的,是'src/store/module/app.js'的addTag方法.
addTag (state, { route, type = 'unshift' }) {
let router = getRouteTitleHandled(route)
if (!routeHasExist(state.tagNavList, router)) {
if (type === 'push') state.tagNavList.push(router)
else {
if (router.name === homeName) state.tagNavList.unshift(router)
else state.tagNavList.splice(1, 0, router)
}
setTagNavListInLocalstorage([...state.tagNavList])
}
},
儘管方法內部仍呼叫了很多,其中一個很重要的判斷,就是routeHasExist(路由是否存在),這個方法也是判斷是否為相同標籤頁的一個關鍵節點(該方法同樣在util.js):
/**
* 判斷開啟的標籤列表裡是否已存在這個新新增的路由物件
*/
export const routeHasExist = (tagNavList, routeItem) => {
let len = tagNavList.length
let res = false
doCustomTimes(len, (index) => {
if (routeEqual(tagNavList[index], routeItem)) res = true
})
return res
}
明顯可以看出,這個方法內呼叫routeEqual,就是用以判斷是否為相同路由的實際方法(當然是通過比較新路由與已有路由進行比較),如此,僅需改變routeEqual即可.
以防萬一,全域性搜尋下呼叫這個routeEqual的所有方法,發現所有呼叫的地方再routeEqual在改變後不會出現新的問題.
3.2 如何實現需求1.2
在進行3.1的操作後,問題得到了部分解決.餘下的問題在於需求1.2沒有得到實現和解決.
首先是,如何實現從列表中開啟或新建的,替換原來的標籤頁,在來回切換後不會回到原來的標籤頁.
只需在app.js中註冊改變標籤頁引數的方法:
// 變更指定路由的引數
changeTagParams (state, route) {
let routeOldIndex = state.tagNavList.findIndex(m => routeEqual(m, route))
if (routeOldIndex !== -1) {
let routeOld = state.tagNavList[routeOldIndex]
routeOld.params = route.params
state.tagNavList.splice(routeOldIndex, 1, routeOld)
setTagNavListInLocalstorage([...state.tagNavList])
}
},
然後在main.vue中對$route的監控最後引用即可.
watch: {
// 檢測route的變化
$route (newRoute) {
const { name, query, params, meta } = newRoute
this.addTag({
route: { name, query, params, meta },
type: 'push'
})
this.setBreadCrumb(newRoute)
this.setTagNavList(getNewTagList(this.tagNavList, newRoute))
this.$refs.sideMenu.updateOpenName(newRoute.name)
// 增加路由引數變更環節
this.changeTagParams(newRoute)
}
},
其次,如果出現像儲存並新增,或者從未儲存到已儲存,這兩種情況來回切換後不會回到原來的情況.
儲存並新增,關鍵是"新增"效果:
// 清空資料,該方法在儲存後呼叫
clearData () {
//該部分是用來清除當前route的引數
this.$router.push({
params: Object.assign(this.$route.params, { id: undefined })
})
//這部分程式碼是用來清空當前頁面內容,每個模組都不盡相同,不必模仿
this.mOtherExpense = JSON.parse(JSON.stringify(this.mOtherExpenseInitial))
this.tableData = [{}]
this.loadCode()
this.mOtherExpense.openingDate = new Date()
},
從未儲存到已儲存,關鍵同樣是如何讓route記住新的id(或其他引數):
// 設定路由id,該方法在第一次儲存後呼叫
setData (id) {
//這裡的id是儲存後從後臺傳來的新id
this.$router.push({
params: Object.assign(this.$route.params, { id })
})
}
4 其他
文中已將本人常用的iview router控制方式提出,或有未涉及者,根據以下了解大概也可解決:
- app.js中的state.tagNavList是標籤頁中顯示的標籤集合;
- 如果要改變一些內容,main.vue中對$route的監控是事件發起的開端,可考慮