- 需求:h5列表页,向上滑动加载下一页。点进详情页,返回列表保持滚动位置不变
- 相关组件:vue中的keep-alive、vux组件库中的Scroller
- 方法:其实就是通过页面组件所在的上层keepAlive组件,暴力操控该对象中的cache列表
- 参考文章:https://github.com/vuejs/vue/issues/6509
- 具体文件
app.vue
<template>
// 先在vuex的state中定义keepAliveList数组(因为include需要绑定数组)
// key是:当前路由,这个路由字符串就相当于属性名
// include数组中包含各个组件的name值,存在数组中的name组件会被缓存
// include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
// https://cn.vuejs.org/v2/api/#keep-alive 详细参考官方文档
<div id="app">
<view-box ref="viewBox">
<keep-alive :include="keepAliveList">
<router-view :key="key"></router-view>
</keep-alive>
</view-box>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'app',
computed: {
...mapGetters(['keepAliveList']),
key() {
return this.$route.path
}
},
data(){
return{
}
},
index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
// 路由文件不需要写meta和beforeEach导航守卫
const createRouter = () => new Router({
routes: [{
path: '/project/list',
name: '项目列表',
component: () => import('@/pages/busModule/project/projList')
},
{
path: '/project/screen',
name: '项目筛选页',
component: () => import('@/pages/busModule/project/projScreen'),
},
{
path: '/project/detail',
name: '项目详情页',
component: () => import('@/pages/busModule/project/projDetail'),
},
{
path: '/project/pay',
name: '项目详情下的付款计划子表',
component: () => import('@/pages/busModule/project/payPlan'),
},
{
path: '/project/contract',
name: '项目详情下的详情合同子表',
component: () => import('@/pages/busModule/project/projContract'),
},
{
path: '/project/payment',
name: '项目详情下的详情付款子表',
component: () => import('@/pages/busModule/project/projPayment'),
}],
})
const router = createRouter()
export default router
vuex
state.js
// 该数组需要绑定给include
const state = {
keepAliveList: []
};
export default state
mutations.js
import * as types from './mutations_types'
const mutations = {
// findIndex 查找满足这个条件的索引,如果传入的name值相等,则获取该name值在数组中的索引
[types.DEL_KEEP_ALIVE_LIST] (state, aliveList) {
const index = state.keepAliveList.findIndex(item => (item === aliveList))
// console.log('index', index)
state.keepAliveList.splice(index, 1)
},
//some 是否有一项满足,传进来的name值在数组中遍历,如果在数组中存在,则return,如果在数组中不存在则push到数组中
[types.SET_KEEP_ALIVE_LIST] (state, aliveList) {
const flag = state.keepAliveList.some(item => (item === aliveList))
if (flag) return
state.keepAliveList.push(aliveList)
},
};
export default mutations
mutations_types.js
export const SET_KEEP_ALIVE_LIST = 'SET_KEEP_ALIVE_LIST';
export const DEL_KEEP_ALIVE_LIST = 'DEL_KEEP_ALIVE_LIST';
actions.js
import * as types from './mutations_types';
const actions = {
set_keep_alive_list: ({
commit
}, aliveList) => {
return new Promise((resolve, reject) => {
commit(types.SET_KEEP_ALIVE_LIST, aliveList);
resolve()
});
},
del_keep_alive_list: ({
commit
}, aliveList) => {
return new Promise((resolve, reject) => {
commit(types.DEL_KEEP_ALIVE_LIST, aliveList);
resolve()
});
}
};
export default actions
getters.js
// getters,可以认为是store的计算属性,就是在某个数据在经过一系列的变化之后,才显示在页面上,这个时候就需要用到计算属性。
const getters = {
keepAliveList: state => state.keepAliveList
};
export default getters
projList.vue
<template>
<scroller v-if="listView" lock-x class="scrollerBox" @on-scroll-bottom="onScrollBottom" @on-scroll="onScroll" ref="scrollerBottom" :scroll-bottom-offst="0" style="padding-bottom:0;">
<div class="box">
<div class="tabHead">
项目列表
</div>
<div v-for="(item, index) in list" :key="index" @click="gotoDetail(item.projId)">
</div>
</div>
</scroller>
</template>
<script>
export default {
// 给当前页面起个名字
name: 'projList',
data(){
return{
list: [],
listParam: {},
timer: null
}
},
activated() {
// 从详情页返回,给当前组件设置记录的滚动高度
this.$nextTick(() => {
this.$refs.scrollerBottom._xscroll.scrollTop(this.scrollTop)
})
},
// 离开路由之前执行的函数
// 如果跳转的是详情页,this.$vnode 相当于当前页面,将当前页面cache(缓存)给属性名为 /project/list 的值
// 再将当前页面的名字存到vuex中
// 如果跳转的不是详情页,那就把这个list页面缓存删掉
beforeRouteLeave(to, from, next) {
if (to.path == '/project/detail') {
// console.log('this.$vnode', this.$vnode)
this.$vnode.parent.componentInstance.cache['/project/list'] = this.$vnode
this.$store.dispatch('set_keep_alive_list', 'projList')
} else {
delete this.$vnode.parent.componentInstance.cache['/project/list']
}
// 跳转下一个页面
next()
},
created () {
this.getList(this.listParam)
},
mounted () {
// 首次进来置为 0
this.$nextTick(() => {
this.$refs.scrollerBottom._xscroll.scrollTop(0)
})
},
methods:{
getList (params) {}
},
// 上拉刷新
onScrollBottom() {
clearTimeout(this.timer)
this.timer = setTimeout(() => {
if (!this.noData) {
let page = ++this.listParam.page
this.listParam.page = page
this.$nextTick(() => {
this.getList(this.listParam)
})
}
}, 200)
},
// 组件api,当组件触发滚动时获取滚动位置到顶部的top值
onScroll(position) {
this.scrollTop = position.top
},
// 跳转详情页
gotoDetail(id) {
this.$router.push({
path: '/project/detail',
query: {objId: id}
})
}
},
}
</script>
- 非滚动组件,记录滚动条位置 (长图详情页跳转子表,返回记录滚动位置)
projDetail.vue
<template>
<div></div>
</template>
<script>
export default {
// 给当前页面起个名字
name: 'projDetail',
data () {
return {
form: {},
detailParams: {
projId: '',
},
}
},
created () {
this.getInfo(this.$route.query.objId)
},
methods: {
getInfo(objId) {},
},
activated() {
// 当从详情页子表回来执行该函数,给详情页设置滚动距离
this.$nextTick(() => {
document.querySelector('#vux_view_box_body').scrollTop = this.scrollTop;
})
},
mounted () {
// 首次进入详情页,滚动高度为0
this.$nextTick(() => {
document.querySelector('#vux_view_box_body').scrollTop = 0;
})
},
beforeRouteLeave(to, from, next) {
// 由于有三个子表,给放到一个数组中
const keys = ['/project/pay', '/project/contract', '/project/payment']
// 如果当前数组中有这一项 则设置当前滚动高度,缓存当前页面,并将当前name值存到vuex中
// 如果去往的路由不在该数组中,则删除当前页面缓存
if (keys.includes(to.path)) {
this.scrollTop = document.querySelector('#vux_view_box_body').scrollTop
this.$vnode.parent.componentInstance.cache['/project/detail'] = this.$vnode
this.$store.dispatch('set_keep_alive_list', 'projDetail')
} else {
delete this.$vnode.parent.componentInstance.cache['/project/detail']
}
next()
}
}
</script>