# shopDemo **Repository Path**: github-26497262/shop-demo ## Basic Information - **Project Name**: shopDemo - **Description**: vue移动端长列表进详情页完整方案 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: plan2 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-03-13 - **Last Updated**: 2025-05-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # vue移动端长列表进详情页完整方案 ## 效果展示 切换到plan1分支,直接看页面也可以。 ## 涉及技术点 - vue - keep-alive - vue-router - router-view - router-link - router.beforeEach ## 一、缓存页面的时机 例如,用户从首页(shop)进入商品列表页(productList),然后进入商品详情页(detail)。这种场景下,什么时候把页面存起来? 市面上常见的方案:一进入商品列表页,立刻将页面缓存起来。本人觉得此方案非常不合理,这里不展开说明,用过的都懂。 本节课方案: 1. 用户进入商品列表页,不做任何操作。 1. 用户前往详情页,缓存商品列表页。 1. 用户返回首页,删除商品列表页的缓存。 ## 二、缓存页面 ### 修改router文件 开发者希望用户从列表页进入详情页时能够缓存列表页。为此,可以在列表页的 meta 标签中添加一个名为 cacheCurrentPage 的属性,属性值为数组 ['detail'],以表明当前页面需要被缓存。参考下面代码 ``` const routes = [ { path: '/shop', name: 'shop', component: () => import('@/views/shop/shop.vue'), meta: { enterPageWithCache: ['productList', 'detail'], } }, { path: '/shop/productList', name: 'productList', component: () => import('@/views/shop/productList.vue'), meta: { enterPageWithCache: ['detail'], } }, { path: '/shop/productList/detail', name: 'detail', component: () => import('@/views/shop/detail.vue'), }, ]; ``` ### 修改app.vue文件 ``` // 这里的代码记得更换 router.beforeEach((to, from: any) => { if (from.meta.cacheCurrentPage) { // 进入的页面在cacheCurrentPage中,缓存当前页面 if (from.meta.cacheCurrentPage.includes(to.name)) { aliveComponent.value.push(from.name as string); } else { // 进入的页面不在cacheCurrentPage中,移除当前页面的缓存 aliveComponent.value = aliveComponent.value.filter( (item) => item !== from.name, ); } } // 去重 aliveComponent.value = [...new Set(aliveComponent.value)]; console.log('aliveComponent.value', aliveComponent.value); }); ``` 通过以上的修改,页面已经被正确缓存起来。 注意这里的 include 中的name,不是router中的name,是组件中的name。 ## 三、记录列表页滚动条位置 ``` const router = createRouter({ history: createWebHistory(), routes, // `routes: routes` 的缩写 scrollBehavior: (to, from, savedPosition) => { if (savedPosition) { return savedPosition; } else { return { top: 0 }; } }, }); ``` 上面的代码是官网给的案例,可以记录滚动条位置。 #### 测试阶段发现的问题 经过测试发现vue2和vue3中keep-alive有一个比较大的区别,keep-alive的缓存机制不同。vue2中缓存DOM树和数据结构;vue3中只缓存数据结构。这样就会引发两个问题。 **问题一:将上述方案放入vue3中发现,vue3中keep-alive只缓存了数据,没有缓存DOM树,如果用户在长页面滚动一段距离后进入了一个短页面,然后再返回到需要滚动到指定位置的长页面时,DOM节点尚未完全渲染出来,页面的高度不够,因此无法滚动到正确的位置。vue2中并不存在这个问题。** 解决方案:给router-view加一个min-height即可。代码如下: ``` const router = createRouter({ history: createWebHistory(), routes, // `routes: routes` 的缩写 scrollBehavior: (to, from, savedPosition) => { if (savedPosition) { // page是router-view的类名 vue2不需要此操作 document.querySelector('#page').style.minHeight = savedPosition.top + window.screen.height + 'px'; return savedPosition; } else { return { top: 0 }; } }, }); ``` 当然scrollBehavior中添加setTimeout也可以,但是这种交互体验不好。 **问题二:vue2中刚进入列表页,立即跳转到详情页,列表页面并没有被缓存起来。 vue3中不存在这个问题** 解决方案:把next放在nextTick中,代码如下: ``` router.beforeEach((to, from, next) => { if (from.meta.cacheCurrentPage) { // 进入的页面在cacheCurrentPage中,缓存当前页面 if (from.meta.cacheCurrentPage.includes(to.name)) { this.aliveComponent.push(from.name); } else { // 进入的页面不在cacheCurrentPage中,移除当前页面的缓存 this.aliveComponent = this.aliveComponent.filter( (item) => item !== from.name, ); } } this.aliveComponent = [...new Set(this.aliveComponent)]; console.log('aliveComponent', this.aliveComponent) this.$nextTick(() => { next(); }); }) ``` ## 四、详情页删除列表页数据 这里直接使用EventBus,传递事件即可。 ``` // main.js import Vue from 'vue' export const EventBus = new Vue(); ``` ``` // detail.js deleteProduct() { EventBus.$emit('deleteProduct', this.productName); this.goBack(); } ``` ``` // list.js created() { EventBus.$on("deleteProduct", (name) => { // 这里的延时器是为了让用户看到删除动画 this.$nextTick(() => { setTimeout(() => { this.listData = this.listData.filter((item) => item.name !== name); }, 500); }); }); }, ``` ## 五、源码链接 master分支中的代码没有使用keep-alive功能。您可以在master分支按照上述方案逐步尝试并体验。 [shopDemo: vue移动端长列表进详情页完整方案 - Gitee.com](https://gitee.com/github-26497262/shop-demo/tree/master/) plan2分支是最终完整的解决方案,将其分成两个分支方便大家对比文件变化。 [shopDemo: vue移动端长列表进详情页完整方案 - Gitee.com](https://gitee.com/github-26497262/shop-demo/tree/plan2/)