zoukankan      html  css  js  c++  java
  • vue 同一个组件的跳转, 返回时保留原来的下拉位置

    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>
    

      

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

  • 相关阅读:
    前序遍历和中序遍历树构造二叉树
    2014百度之星初赛第一场部分题解
    爬虫小记--抓取过程简要分析
    前端程序猿必知:单页面应用的核心
    swift -类的定义及使用
    【Unity优化】怎样实现Unity编辑器中的协程
    poj 1125 (floyed 最短路径)
    Android API Guides---Tasks and Back Stack
    循环-16. 猴子吃桃问题(15)
    零java基础搞定微信Server
  • 原文地址:https://www.cnblogs.com/wjyz/p/10194720.html
Copyright © 2011-2022 走看看