本次需求:
当前列表页可进行条件搜索,有一些操作例如:查看详情,编辑等需要跳转路由。现在是想要在返回列表页时保留之前的搜索项等
需要解决的问题:
1.详情页面,或者编辑页但是不进行编辑直接返回
2.编辑操作成功之后进行返回。
解决思路:
第一种场景:没有对数据进行任何操作,可以看做是只进行了查看,这类问题可以用keep alive解决
第二种场景:对数据进行了操作,所以返回列表页的时候必须保证列表页数据时最新的状态。比如当前数据是暂存状态,是可以对数据进行修改的,也就是列表页有个修改按钮,这个按钮是只有暂存状态才应该展示。当前数据修改提交完成后,应该是没有修改按钮的,如果用keep alive的话数据还是旧的数据和状态,这样会导致依然存在修改按钮,所以这种场景中需要刷新表格,也就是需要把搜索项和分页等列表页的参数进行保存。
这两种场景下文为了方便可能统一称呼为缓存起来。
具体的解决步骤和遇到的问题
在实现之前需要明白,虽然是两种方式。但是实际中第一种场景可单独存在(只是详情页面,没有对数据进行修改的操作),第二种场景必定与第一种共存(因为你的比如修改页码,可以进行修改,但是也可以不修改直接返回)
如下的所有代码基本都是伪代码,可借鉴思路,具体实现还是要根据自己的实际情况进行修改。(因为我有部分实现就是和自己的逻辑组件之类的耦合在了一起,主要是因为懒,就删删减减复制了过来)
代码块中的注释为了不出现滚动条所以很多分成了两行,希望不会影响阅读体验
keep alive
通过keep alive的include设置都有哪些页面需要进行缓存,这个keepAliveList数组(需要被缓存页面name所组成的)保存在vuex中
store/modules/keepAlive.js
const state = {
keepAliveList: []
}
const mutations = {
SET_KEEP_ALIVE_LIST: (state, pageName) => {
if (!state.keepAliveList.includes(pageName)) {
state.keepAliveList = [...state.keepAliveList, pageName]
}
},
DEL_KEEP_ALIVE_LIST: (state, pageName) => {
if (state.keepAliveList.includes(pageName)) {
const index = state.keepAliveList.findIndex(item => item === pageName)
state.keepAliveList.splice(index, 1)
}
}
}
keep alive遇到的问题:
最开始的基本思路是:
在list页面做判断,如果去往的页面是detail页面,那么缓存list页面,否则删除。
list页面
beforeRouteLeave(to, from, next) {
if(to.path === '详情') {
this.$store.commit('keepAlive/SET_KEEP_ALIVE_LIST', this.$option.name)
} else {
this.$store.commit('keepAlive/DEL_KEEP_ALIVE_LIST', this.$option.name)
}
next()
}
这种实现方式出现的问题:
第一次从list页面跳转详情,从详情返回可以正常缓存。如果是第二次从list页面跳转详情,从详情返回list页面会发现既触发created,又触发了activated
后来发现其实是思路错了,改为在进入list页面的时候进行缓存,如果跳转的不是detail页面,那么删除当前页面的缓存
beforeRouteEnter(to, from, next) {
next(vm => {
vm.$store.commit('keepAlive/SET_KEEP_ALIVE_LIST', vm.$options.name)
})
},
beforeRouteLeave(to, from, next) {
if (to.path !== '详情') {
this.$store.commit('keepAlive/DEL_KEEP_ALIVE_LIST', this.$options.name)
}
next()
}
保留搜索条件:
点击查询的时候把当前页面的搜索参数和页码存在vuex中,然后在当前list页面判断vuex中是否有搜索项,如果有的话会搜索这些条件。
这里面需要注意的是:vuex中有数据,但是不是所有时候都需要搜索这些条件。只有是从当前list页面跳转出去,然后返回list页面才需要搜索条件
vuex代码:
SET_PAGE_PARAMS: (state, obj) => {
Object.keys(obj).forEach(k => {
state.pageParams = {
[k]: {
...state.pageParams[k],
...obj[k]
}
}
})
},
DEL_PAGE_PARAMS: (state, name) => {
if (!name) return
delete state.pageParams[name]
}
搜索区域所在页面或组件代码
// pageParams是除page和size之外的搜索条件
// 因为我的搜索和表格分属两个组件,所以保留参数这部分也分开了
this.$store.commit('keepAlive/SET_PAGE_PARAMS', {搜索条件})
表格区域所在页面或组件代码
created() {
const parentName = this.$parent.$options.name
if (Object.keys(this.pageParams).includes(parentName)) {
const { searchCondition = {}} = this.pageParams[parentName]
Object.keys(searchCondition).forEach((item) => {
tableSearchParams[item] = searchCondition[item]
})
}
if (this.pageParams[parentName]) {
this.currentPage = this.pageParams[parentName].page || 1
this.currentSize = this.pageParams[parentName].size || 10
}
this.getList({ initialParams: tableSearchParams })}
// 同样是表格所在页面或组件代码,要在getList方法里面判断
// 如果当前list页面的name在keepAliveList中的话,也就意味着这个页面需要被缓存
getList({ initialParams = {} } = {}) {
const pageParams = {
page: this.currentPage,
size: this.currentSize
}
const parentName = this.$parent.$options.name
if (this.keepAliveList.includes(parentName)) {
const tempObj = {
[this.$parent.$options.name]: pageParams
}
this.$store.commit('keepAlive/SET_PAGE_PARAMS', tempObj)
}
...
}
list页面代码
// list页面原有的跳转路由代码中额外增加
// 这个needKeepAlive变量是否需要被缓存或者说是否需要保留搜索参数。
this.$store.commit('keepAlive/SET_NEED_KEEP_ALIVE', true)
// ---分割线---
created() {
this.$store.commit('keepAlive/SET_NEED_KEEP_ALIVE', false)
},
activated() {
this.$store.commit('keepAlive/SET_NEED_KEEP_ALIVE', false)
},
beforeRouteLeave(to, from, next) {
// 这个routeList是list页面所有跳转出去的路由组成的数组。
if (!this.routeList.includes(to.path) || !this.needKeepAlive) {
this.$store.commit('keepAlive/DEL_KEEP_ALIVE_LIST', this.$options.name)
this.$store.commit('keepAlive/DEL_PAGE_PARAMS', this.$options.name)
}
// 从list页面调到详情或者编辑页面之后,只有返回当前list页面才有是否需要缓存这种考虑
// 如果不是返回当前list页面就把当前页面的搜索参数和keepAliveList对应清空就行
if (this.needKeepAlive) {
to.meta.isKeepAlive = true
to.meta.fromPath = this.$route.path
to.meta.fromName = this.$options.name
}
next()
}
详情或者编辑页面
beforeRouteLeave(to, from, next) {
const { isKeepAlive = false, fromPath = '', fromName = '' } = this.$route.meta
if (to.path !== fromPath || !isKeepAlive || !this.keepAliveMode) {
this.$store.commit('keepAlive/DEL_PAGE_PARAMS', fromName)
this.$store.commit('keepAlive/DEL_KEEP_ALIVE_LIST', fromName)
return next()
}
if (this.keepAliveMode === 'update') {
this.$store.commit('keepAlive/DEL_KEEP_ALIVE_LIST', fromName)
return next()
}
next()
}
// 同时在操作成功返回中增加
this.keepAliveMode = 'update'
// 在单纯返回上一页面中增加
this.keepAliveMode = 'detail'
保留数据到这里就成功了,不过还有个东西要处理,就是search组件。如果有初始条件的话,需要展示这些默认值。(默认值展示部分就不做过多展开了。一般情况就是v-model的值变成你保存的数据即可)
search中一般而言了包含级联下拉这种情况,也就是选择了前一项下拉,根据值调用下一个下拉的接口。(这部分记得特殊处理,因为我这部分和我本身的search组件有点耦合太高,所以也没有复制过来。。。)
搜索条件默认展示时小问题
一般是先调用完下拉选项的接口,再赋默认值。我是相当于把所有的下拉选项接口保存在一个数组中,然后循环调用。
但是用forEach循环的话不能保证接口结束完再执行赋默认值,需要用for of
,这部分就不额外展开了,关于await
执行顺序问题可以参考:
正确写法
created() {
...
for (const item of cascadeArr) {
const val = searchCondition[item.prop]
await this.selChange(xxx)
}
// 这里再赋默认值
}
methods: {
async selChange() {
// 调用接口
const res = await xxxxx()
....
}
}
错误写法
created() {
...
cascadeArr.forEach(item => {
const val = searchCondition[item.prop]
await this.selChange(xxx)
})
// 这里赋默认值并不能保证接口已经返回结果了
}
END
如果有看到这里的大佬有好的实现方法希望不吝赐教