vue项目详情页返回列表,记录之前信息
需求场景:
我们在开发后台管理系统的时候,经常会遇到这样的问题,我刚通过筛选条件筛选出来了一批数据,然后我点击到了第二页,点击进入了对应数据的详情页,查看完数据后,返回列表,这个时候之前筛选的信息不见了,页码也回到了第一页;这个时候如果需要再筛选,再重复操作的话,这样的体验是非常差的,很浪费时间;最好的体验是:我去详情之前是怎样的页面,详情返回后就是怎样的页面,保留之前的筛选条件、列表数据、分页信息等,甚至刷新也都保存之前的筛选条件;但是如果我从其他模块进入,我希望筛选条件等是清空的,从第一页开始;
具体展示如下:
image解决方案:
方案一:将筛选条件与分页信息放到url链接中
因为该方案较简单,也是最易懂的,所以这边就不多赘述;
优点:
1、简单粗暴;
2、刷新后也是能保存筛选的条件
缺点:
1、如果筛选条件比较多,链接会很长;
2、因为链接中有很多信息,不太安全与美观;
3、对于面包屑导航栏不好实现;因为面包屑导航栏中不光是对应的路由,还要把对应参数都要拼接上去,会非常麻烦
方案二:使用子路由实现,一层一层覆盖上去
因为该方案也是比较简单,所以这边也不多赘述;
优点:
1、是最简单的,不需要做什么其他操作
缺点:
1、如果该模块有好几层,那路由设计时候就会很复杂,而且必须层层嵌套;嵌套多层会有性能问题
2、刷新之后就恢复之前的数据了;如果做本地存储的话会非常麻烦
方案三:采用keep-alive
该方案能记录所有的操作,但是去复原会是非常麻烦的一个事情;意思就是:我不同模块切换,需要重制对应模块的筛选条件,这个时候必须手动去清数据;网上也有说使用keep-alive的 incloud来切换需要缓存的组建;但是在自己亲测后,发现存在缓存错乱的问题;也使用过离开后调用$destroy 销毁组建,但是一旦销毁过一次后,之后都不会缓存了;并且刷新后是不会保存之前的筛选条件与内容的;所以弃用
方案四:
最终还是使用vuex + mixins来实现这样的效果,并且是非常简单;
思路:
1、刚开始想每个组件的筛选参数都存一份,把所有的筛选信息、分页信息、列表信息等都存到单独模块的store中,每次修改的时候去更新store信息;但是后来发现,如果模块很多,逻辑会非常复杂,代码量也会非常多;后来做了统一存储在一个store中,然后需要的时候去存,不需要的时候去删,根据对应页面的routeName来做区分;
2、写一个专门用来处理该逻辑的mixins文件,里面放各个模块都会用到的生命周期函数与method函数;然后统一在函数中做更新数据的操作;
3、如何判断从store中拿已存储的值还是说拿初始值?这个判断是在beforeRouteEnter这个路由生命周期函数中进行,在该生命周期中通过to与from的path,判断了路由是前进还是后退,如果是前进,那拿默认的初始值,并且销毁store中存储的信息;如果是返回,则取store中存储的值;关于如何判断,待会儿会详细说明;
4、关于参数初始值,为了方便管理与复用,所以拉到了一个单独的js文件中,做统一管理;
5、所有需要实现这样功能的页面都引入该mixins文件,然后单个组件该怎么搞就怎么搞,不需要做特殊处理;
说了这么多屁话,直接撸代码:
具体实现:
各个列表,需要实现该功能的所有列表请求参数都放到该js文件中,方便统一管理并且赋值;
reqDataList.js
// 角色管理列表请求参数初始化数据
export let roleManageReqData = {
type: '',
searchContent: '',
page: 1,
pageSize: 10
}
// 角色详情列表请求参数初始化数据
export let roleDetailReqData = {
type: '',
searchContent: '',
page: 1,
pageSize: 10
}
// 用户管理列表请求参数初始化数据
export let userManageReqData = {
type: '',
searchContent: '',
page: 1,
pageSize: 10
}
这边路由设计是平级路由,但是path的命名是按模块嵌套来的,这样设计可以较方便的判断是前进还是后退;
routerMap.js
{
path: '/userManage',
name: 'userManage',
title: '人员管理',
meta: {},
component: () => import('@/views/peopleManage/userManage/index.vue'),
},
{
path: '/userManage/addUser',
name: 'addUser',
title: '新增用户',
meta: {},
component: () => import('@/views/peopleManage/userManage/addUser/index.vue')
},
{
path: '/roleManage/roleDetail',
name: 'roleDetail',
title: '角色详情',
meta: {},
component: () => import('@/views/peopleManage/powerManage/roleManage/roleDetail/index.vue')
},
{
path: '/roleManage/roleDetail/roleEdit',
name: 'roleEdit',
title: '角色编辑',
meta: {},
component: () => import('@/views/peopleManage/powerManage/roleManage/roleDetail/roleEdit/index.vue')
},
{
path: '/roleManage',
name: 'roleManage',
meta: {},
component: () => import('@/views/peopleManage/powerManage/roleManage/index.vue')
},
{
path: '/roleManage/addRole',
name: 'addRole',
title: '新增角色',
meta: {},
component: () => import('@/views/peopleManage/powerManage/roleManage/addRole/index.vue')
}
列表混合页面;公共的一些函数、操作都放在这里面;这边说明一下混合中的beforeRouteEnter与组件中的beforeRouteEnter执行顺序,如下图:
image所以在1中进行判断采用缓存还是初始化的值,然后赋值给路由meta信息;2中拿到对应的meta中的参数信息,请求接口,请求成功后next,在3中做对应的组件内部赋值等;在4中将对应组件的筛选条件的值赋值为第1步中的值;
pagitionMixin.js
import store from '@/store/index'
import * as reqDataList from '@/config/reqDataList'
export default {
data() {
return {
reqData: {...reqDataList[this.$route.name + 'reqData']} // 根据当前routeName,获取对应初始化的参数
}
},
beforeRouteEnter (to, from, next) {
// 是否是返回;这边path设计是分层级的,如:列表的路由是:/list ,详情就是:/list/detail;这样就可以通过以下方式判断,是前进还是后退了
let isBack = from.path.indexOf(to.path) > -1
// 从store里面拿到存储的值
let reqData = store.state.publicInfo.reqDataList[to.name]
// 把reqData存储到对应路由的meta信息中,供单个组件beforeRouteEnter生命周期使用,解决没有next(),拿不到this的问题;
to.meta.reqData = isBack && reqData ? reqData : {...reqDataList[to.name + 'reqData']}
if (!isBack) {
console.log('前进')
// 如果是前进,则清除store中存储的该组建的数据
store.dispatch('uploadListParams', {
routeName: to.name
})
} else {
console.log('后退', store.state.publicInfo.reqDataList[to.name])
}
next(vm => {
// 请求参数做赋值
vm.reqData = to.meta.reqData
})
},
methods: {
/**
* @func 列表搜索
*/
search () {
console.log('搜索')
this.reqData.page = 1
store.dispatch('uploadListParams', {
routeName: this.$route.name,
reqData: this.reqData
})
this.getList ()
},
/**
* @func 列表分页
* @param val 当前第几页
*/
currentChange (val) {
console.log('分页')
this.reqData.page = val
store.dispatch('uploadListParams', {
routeName: this.$route.name,
reqData: {
...this.reqData,
page: val
}
})
this.getList (val)
}
}
}
单个组件内正常书写,这边只截取beforeRouterEnter中的代码;这边采用先请求,后next,是因为需要实现进度条效果;当该组件请求完成后,再切入到该路由中,可以达到与github一样的效果;
roleManage/index****.vue
beforeRouteEnter (to, from, next) {
getUserList(to.meta.reqData).then(res => {
next(vm => {
vm.tableData = res.data.list
vm.totalCount = res.data.totalCount
})
})
}
在store中处理对应筛选条件,对应路由的name为key,筛选参数对象为value,存储在reqDataList中;如果已经存在则更新,如果不存在则新增;如果没有传递reqData参数,则表示删除;每一次更新都同步一下sessionStorage中的数据,这边存储用于刷新后还是可以保留之前的筛选条件
publicInfo/index.js
import * as actions from './actions'
import * as getters from './getters'
import * as types from './types'
const publicInfo = JSON.parse(sessionStorage.getItem('publicInfo'))
const state = Object.assign({
reqDataList: {}
}, publicInfo);
const mutations = {
/**
* 更新列表请求参数
*
*/
[types.UPLOAD_LIST_PARAMS](state, data) {
try {
// 判断是否有reqData,如果存在,表示是新增或者更新,不然则是删除
if (data.reqData) {
state.reqDataList[data.routeName] = data.reqData
} else {
delete state.reqDataList[data.routeName]
}
// 更新后的数据存储到本地
sessionStorage.setItem('publicInfo', JSON.stringify(state));
} catch (err) {
console.log("存储错误:" + err)
}
}
}
export default {
state,
getters,
actions,
mutations
}
这样就能完美的实现该功能;具体的demo地址,请点击这里;