1,需求分析

  公司的项目有这样一个需求: 同一个list组件,根据传过来的listId渲染成多个页面,每个页面都可以下拉。在返回到不同的list页面时,要保留当时下拉的位置。

  说的我自己都有点懵逼了,画个图来示范下吧!

  

     demo

    github地址

   这三个页面都总用的list.vue这个组件。如果三个页面都渲染后,通过上方的导航,可以跳到对应的list页面,当然,也要保留当时下拉的位置。由于这几个list页面有下拉,都用的mescroll作为下拉组件。

2,代码分析

  看到这个需求,当时第一时间想的就是keep-alive,但是使用keep-alive的话第二次进入list页面,页面还是之前的内容。当然,可以监听$route来进行判断加载,但这样的话就保存不了之前的下拉位置。

  当然有同学说可以把之前的位置存起来,如果返回到这个页面,那就直接下拉到这个位置,这种方法我没试过,但由于list里面的内容是动态加载的,如果我第一个list加载了两页, 进入第二个list,再进入第一个list,这时候数据是重新加载的,就算保存了之前的位置,那最多也是下拉到第一页,第二页根本拉不出来。

  这里参考了github里面关于这方面的一个组件vue-navigation

  

  这位老哥的思路是,每进一个页面,就给$route的query里面加一个唯一标识码,然后将这个标识码存在sessionStorage里面,将这个页面的vue实例缓存起来。后面每进一个页面,就和session里面保存的进行比较,如果之前存过,就把缓存的vue实例直接渲染出来,没有存过就继续新建标识码,缓存vue实例。  退后的时候就将session里面对应的删除,缓存的vue实例也删除。

  这位老哥做的和我需要的功能已经很接近了,如果大家是做的移动端单页面,没有我这种恶心的导航条,就可以直接用了,很方便。

  但是还是有点区别,就是他的只能前进和后退,不能随便进到之前保存的页面,而我现在的需求是要随便跳。

  那就在这里将老哥的和我改进的说一下吧:

   util.js

export function genKey () {        // 设置一个8位随机的字符串,
const t = 'xxxxxxxx'
return t.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
} export function getKey (route, keyName) {
return `${route.name || route.path}?${route.query[keyName]}`
} function isRegExp (pattern) {
return Object.prototype.toString.call(pattern).substr(8, 6).toLocaleLowerCase() === 'regexp'
} export function matches (pattern, name) {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
return false
} export function isEqualObj (obj1, obj2) { // 比较两个对象是否相等
if (obj1 === obj2) return true
const key1 = Object.getOwnPropertyNames(obj1)
const key2 = Object.getOwnPropertyNames(obj2)
if (key1.length !== key2.length) return false
for (const k of key1) {
if (obj1[k] !== obj2[k]) {
return false
}
}
return true
} export function _defineProperty (obj, key, value) { // 如果原对象有key的值,那么就保留,不然就新加一个属性,值为value
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
configurable: true,
enumerable: true,
writable: true
})
} else {
obj[key] = value
}
return obj
}

 

  router.js

let routers = []

if (window.sessionStorage.WX_REPETITION) {
routers = JSON.parse(window.sessionStorage.WX_REPETITION)
} export default routers

   index.js   可以使我们的组件能像vue.use(xxx) 这样使用

import Routers from './router'
import Repetition from './Repetition'
import {getKey, genKey, isEqualObj, _defineProperty} from './utils' export default {
install: (Vue, {router, store, moduleName = 'repetition' ,keyName = 'WXK'} = {}) => {
if (!router || !store) {
throw new Error('this component need options router and store')
} store.registerModule(moduleName, {
state: {
visitedView: []
},
mutations: {
addViews: (state, view) => {
state.visitedView.push(view)
},
deleteViews: (state, view) => {
for (let k in state.visitedView) {
if (state.visitedView[k].route.path === view.route.path) {
state.visitedView.splice(k, 1)
break
}
}
},
emptyViews: (state, view) => {
state.visitedView.splice(0)
}
}
}) router.beforeEach((to, from, next) => { // 在进入之前,检查sessionStorage里面保存的已经打开的页面的记录,如果有记录,则将之前的标识码拿过来用,如果没有,则从新创建一个标识码,然后放到query里面,next出去
if (!to.query[keyName]) { //
let query = {...to.query}
let routers = Routers
let dif = true
for (let route of routers) {
if (route[1].path === to.path && isEqualObj(Object.assign({}, to.query, _defineProperty({}, keyName, null)), Object.assign({}, route[1].query, _defineProperty({}, keyName, null)))) {
dif = false
query[keyName] = route[1].query[keyName]
next({name: to.name, params: to.params, query: query})
return
}
}
if (dif) {
query[keyName] = genKey()
next({name: to.name, params: to.params, query: query})
}
} else {
next()
}
}) router.afterEach((to, from) => { // 判断这次进入的地址在sessionStorage里面是否存在,如果存在就直接出去, 如果不存在就存进sessionStorage
let routers = Routers
const name = getKey(to, keyName)
for (let route of routers) {
if (route.indexOf(name) > -1) {
return
}
}
routers.push({name: to.name, query: to.query, path: to.path})
window.sessionStorage.WX_REPETITION = JSON.stringify(routers)
}) Vue.component('repetition', Repetition(moduleName, keyName)) // 注册该组件
}
}

 

  Repetition.js  组件的定义函数

import {getKey} from './utils'

export default (moduleName, keyName) => {
return {
name: 'repetition',
data: () => {
return {
}
},
created () {
this.cache = new Map() // 缓存每个打开的页面的实例
},
render () {
const vnode = this.$slots.default ? this.$slots.default[0] :null
if (vnode) {
vnode.key = vnode.key || (vnode.isComment ? 'comment' : vnode.tag)
const key = getKey(this.$route, keyName)
if (vnode.key.indexOf(key) === -1) {
vnode.key = `repetition-${key}-${vnode.key}`
}
if (this.cache.has(key)) { // 如果cache里面已经保存了需要打开的页面,那么就直接将cache里面保存的实例赋给当前的vnode
vnode.componentInstance = this.cache.get(key).componentInstance
let visiteds = this.$store.state[moduleName].visitedView.map(item => {
return getKey(item, keyName)
})
for (let cacheKey of this.cache.keys()) { // 这里是我自己项目中需要的,因为要用到导航条
if (visiteds.indexOf(cacheKey) === -1) {
this.cache.get(cacheKey).componentInstance.$destroy()
this.cache.delete(cacheKey)
break
}
}
} else { // 如果cache里面没有需要打开的页面,将当前vnode存到cache,
this.cache.set(key, vnode)
this.$nextTick(() => {
let route = this.$route
this.$store.commit(`${moduleName}/addViews`, {
name: route.name,
path: route.path,
query: route.query
})
})
}
vnode.data.keepAlive = true
} else {
if (this.$store.state[moduleName].visitedView.length === 0 && this.cache.size) {
let key0 = [...this.cache.keys()]
this.cache.get(key0[0]).componentInstance.$destroy()
this.cache.delete(key0[0])
}
}
return vnode
}
}
}

  导航条那里的vue页面:

  

<template>
<router-link
v-for="tag in visitedView"
:key="tag.path"
:class="{active:isActive(tag)}"
:to="{path:tag.route.path,query:tag.route.query,fullPath:tag.route.fullPath}"
tag="span"
class="tags-item"
>
{{tag.title}}<span class="el-icon-close" @click.stop="closeSelectTag(tag)"></span></router-link>
</template> <script>
import Routers from './router' // 这里还是用的上面的router.js
export default {
name: 'tagsView',
data () {
return {
}
},
methods: {
closeSelectTag (tag) { // 关闭当前页面
let routers = Routers
console.log(routers)
console.log(tag.route.query.WXK)
for (let i in routers) {
if (routers[i][1].query.WXK === tag.route.query.WXK) {
routers.splice(i, 1)
}
}
window.sessionStorage.WX_REPETITION = JSON.stringify(routers)
this.$store.commit('tagsView/deleteviews', tag)
if (this.visitedView.length === 0) {
this.$router.push({name: 'home'})
} else {
let visit = this.visitedView[this.visitedView.length - 1]
this.$router.push({path: visit.route.path})
}
},
isActive (tag) {
return tag.route.path === this.$route.path
}
},
mounted () {
},
computed: {
visitedView () {
return this.$store.state.repetition.visitedView
}
},
watch: {
}
}
</script>

  

  这样下来,这个需求就算是差不多完成了,写的有点乱,因为是根据自己的需求改的,大部分都是借鉴了别人的方法,所以就只是在这里展示一下。

vue 同一个组件的跳转, 返回时保留原来的下拉位置的更多相关文章

  1. 在AJAX里 使用【 XML 】 返回数据类型 实现简单的下拉菜单数据

    在AJAX里 使用XML返回数据类型 实现简单的下拉菜单数据 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN ...

  2. 在AJAX里 使用【 JSON 】 返回数据类型 实现简单的下拉菜单数据

    在AJAX里 使用JSON返回数据类型 实现简单的下拉菜单数据 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//E ...

  3. Bootstrap modal模态框关闭时,combobox input下拉框仍然保留在页面上

    问题描述: 当点击模态框的关闭按钮时,下拉框中的内容没有消失,而是移动到了页面左上角 分析:这个问题的定位在于是用的哪种模态框,bootstrap和easyui都可以实现模态框,但是两个方法实现的模态 ...

  4. vue多个路由复用同一个组件的跳转问题(this.router.push)

    因为router-view传参问题无法解决,比较麻烦. 所以我采取的是@click+this.router.push来跳转 但是现在的问题是跳转后,url改变了,但是页面的数据没有重新渲染,要刷新才可 ...

  5. js点击时关闭该范围下拉菜单之外的菜单

    $(function(){ $(document).bind("click",function(e){ //id为menu的是菜单 if($(e.target).closest(& ...

  6. layui编辑商品时,怎么使用下拉菜单显示商品默认分类的问题

    //加载商品默认的分类$.get('/admin/category/selec/' + {$simple.0.first_pid},function(msg){ $("#two_cate&q ...

  7. Vue动态组件

    前面的话 让多个组件使用同一个挂载点,并动态切换,这就是动态组件.本文将详细介绍Vue动态组件 概述 通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,可以实现动 ...

  8. 关于Vue父子组件传值(复杂数据类型的值)的细节点

    vue 父子组件传值是很常见的,多数情况下都是父传递给子的值是基础数据类型,如string,number,boolean, 当父组件值被修改时,子组件能够实时的作出改变. 如果父子传值的类型是复杂数据 ...

  9. vue自定义下拉框组件

    创建下拉框组件 Select.vue <template> <div class="selects"> <div :class="{sele ...

随机推荐

  1. 747. Largest Number At Least Twice of Others比所有数字都大两倍的最大数

    [抄题]: In a given integer array nums, there is always exactly one largest element. Find whether the l ...

  2. adb shell unauthorized问题

    出现unauthorized 一般插上usb后,手机会弹出一个要求你授权debugging的对话框,如果没有的话,就是rsa_key有问题: /adb_keys. User-installed key ...

  3. Luogu 3241 [HNOI2015]开店

    BZOJ 4012权限题 浙科协的网突然炸了,好慌…… 据说正解是动态点分治,然而我并不会,我选择树链剖分 + 主席树维护. 设$dis_i$表示$i$到$root(1)$的值,那么对于一个询问$u$ ...

  4. C#使用var定义变量时的四个特点

    使用var定义变量时有以下四个特点: 1. 必须在定义时初始化.也就是必须是var s = “abcd”形式: 2. 一但初始化完成,就不能再给变量赋与初始化值类型不同的值了. 3.   var要求是 ...

  5. 过渡函数transition-timing-function

  6. GTA4下载和玩教程

    侠盗猎车4中文版.rar: 但是下载安装之后总是在刚开始开车的时候跳转到人物界面卡在那里无法进行下去,解决办法: 1.新建一个commandline.txt文件复制以下内容进去 -novblank - ...

  7. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  8. 使用Recyclerview实现图片水平自动循环滚动

    简介: 本篇博客主要介绍的是如何使用RecyclerView实现图片水平方向自动循环(跑马灯效果) 效果图: 思路: 1.准备m张图片 1.使用Recyclerview实现,返回无数个(实际Inter ...

  9. HTML5 Canvas核心技术图形动画与游戏开发 ((美)David Geary) 中文PDF扫描版​

    <html5 canvas核心技术:图形.动画与游戏开发>是html5 canvas领域的标杆之作,也是迄今为止该领域内容最为全面和深入的著作之一,是公认的权威经典.amazon五星级超级 ...

  10. C#上位机中ZedGraph控件的使用

    上位机程序控制PLC模拟量通道输出周期性正弦波信号,并采集所造成改变的模拟量输入信号,并绘制数据变化曲线. 界面如图: 最后测试效果如图: 代码: using System; using System ...