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地址,请点击这里;