当前位置:首页 » 《随便一记》 » 正文

Vue实现TagView标签导航栏缓存,解决重新进去页面不刷新问题

19 人参与  2023年04月09日 14:50  分类 : 《随便一记》  评论

点击全文阅读


实现的效果图

一、先实现导航栏的效果

1、先在@/layout/components/Sidebar目录下新建一个组件ScrollPane.vue,把下面代码复制进去

<template>  <div ref="scrollContainer" class="scroll-container" @wheel.prevent="handleScroll">    <div ref="scrollWrapper" class="scroll-wrapper" :style="{left: left + 'px'}">      <slot />    </div>  </div></template><script>const padding = 15 // tag's paddingexport default {  name: 'ScrollPane',  data() {    return {      left: 0    }  },  methods: {    handleScroll(e) {      const eventDelta = e.wheelDelta || -e.deltaY * 3// wheelDelta:-120;deltaY:-120      const $container = this.$refs.scrollContainer// 外面的container      const $containerWidth = $container.offsetWidth// 外面的container的宽度      const $wrapper = this.$refs.scrollWrapper// 里面      const $wrapperWidth = $wrapper.offsetWidth// 里面的宽度      if (eventDelta > 0) {        this.left = Math.min(0, this.left + eventDelta)// min() 方法可返回指定的数字中带有最低值的数字。      } else {        if ($containerWidth - padding < $wrapperWidth) {          if (this.left < -($wrapperWidth - $containerWidth + padding)) {            this.left = 1          } else {            this.left = Math.max(this.left + eventDelta, $containerWidth - $wrapperWidth - padding)          }        } else {          this.left = 0        }      }    },    moveToTarget($target) {      const $container = this.$refs.scrollContainer      const $containerWidth = $container.offsetWidth      const $targetLeft = $target.offsetLeft      const $targetWidth = $target.offsetWidth      if ($targetLeft < -this.left) {        // tag in the left        this.left = -$targetLeft + padding      } else if ($targetLeft + padding > -this.left && $targetLeft + $targetWidth < -this.left + $containerWidth - padding) {        // tag in the current view          // eslint-disable-line      } else {        // tag in the right        this.left = -($targetLeft - ($containerWidth - $targetWidth) + padding)      }    }  }}</script><style rel="stylesheet/scss" lang="scss" scoped>  .scroll-container {    white-space: nowrap;    position: relative;    overflow: hidden;    width: 100%;    .scroll-wrapper {      position: absolute;    }  }</style>

2、在相同目录下再新建一个TagsView.vue组件,把下面代码复制进去

<template>  <div class="tags-view-container">    <scroll-pane ref="scrollPane" class="tags-view-wrapper">      <router-link        v-for="tag in Array.from(visitedViews)"        ref="tag"        :key="tag.path"        :class="isActive(tag)?'active':''"        :to="tag"        class="tags-view-item"        @contextmenu.prevent.native="openMenu(tag,$event)"      >        {{ tag.title }}        <span class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />      </router-link>    </scroll-pane>    <ul v-show="visible" class="contextmenu" :style="{left:left+'px',top:top+'px'}">      <li @click="closeSelectedTag(selectedTag)">关闭</li>      <li @click="closeOthersTags">关闭其他</li>      <li @click="closeAllTags">关闭所有</li>    </ul>  </div></template><script>import ScrollPane from './ScrollPane.vue'export default {  name: 'TagsView',  components: { ScrollPane },  data() {    return {      visible: false,      top: 0,      left: 0,      selectedTag: {}    }  },  computed: {    visitedViews() {      return this.$store.state.tagsView.visitedViews    }  },  watch: {    $route() {      this.addViewTags()      this.moveToCurrentTag()    },    visible(value) {      if (value) {        document.body.addEventListener('click', this.closeMenu)      } else {        document.body.removeEventListener('click', this.closeMenu)      }    }  },  mounted() {    this.addViewTags()  },  methods: {    generateRoute() {      if (this.$route.name) {        return this.$route      }      return false    },    isActive(route) {      return route.path === this.$route.path    },    addViewTags() {      const route = this.generateRoute()      if (!route) {        return false      }      this.$store.dispatch('addVisitedViews', route)    },    moveToCurrentTag() {      const tags = this.$refs.tag      this.$nextTick(() => {        for (const tag of tags) {          if (tag.to.path === this.$route.path) {            this.$refs.scrollPane.moveToTarget(tag.$el)            break          }        }      })    },    closeSelectedTag(view) {      this.$store.dispatch('delVisitedViews', view).then((views) => {        if (this.isActive(view)) {          const latestView = views.slice(-1)[0]          if (latestView) {            this.$router.push(latestView)          } else {            this.$router.push('/')          }        }      })    },    closeOthersTags() {      this.$router.push(this.selectedTag)      this.$store.dispatch('delOthersViews', this.selectedTag).then(() => {        this.moveToCurrentTag()      })    },    closeAllTags() {      this.$store.dispatch('delAllViews')      this.$router.push('/')    },    openMenu(tag, e) {      this.visible = true      this.selectedTag = tag      const offsetLeft = this.$el.getBoundingClientRect().left // container margin left      this.left = e.clientX - offsetLeft + 15 // 15: margin right      this.top = e.clientY    },    closeMenu() {      this.visible = false    }  }}</script><style rel="stylesheet/scss" lang="scss" scoped>  .tags-view-container {    .tags-view-wrapper {      background: #fff;      height: 34px;      border-bottom: 1px solid #d8dce5;      box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);      .tags-view-item {        display: inline-block;        position: relative;        height: 26px;        line-height: 26px;        border: 1px solid #d8dce5;        color: #495060;        background: #fff;        padding: 0 8px;        font-size: 12px;        margin-left: 5px;        margin-top: 4px;        &:first-of-type {          margin-left: 15px;        }        &.active {          background-color: #42b983;          color: #fff;          border-color: #42b983;          &::before {            content: '';            background: #fff;            display: inline-block;            width: 8px;            height: 8px;            border-radius: 50%;            position: relative;            margin-right: 2px;          }        }      }    }    .contextmenu {      margin: 0;      background: #fff;      z-index: 100;      position: absolute;      list-style-type: none;      padding: 5px 0;      border-radius: 4px;      font-size: 12px;      font-weight: 400;      color: #333;      box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);      li {        margin: 0;        padding: 7px 16px;        cursor: pointer;        &:hover {          background: #eee;        }      }    }  }</style><style rel="stylesheet/scss" lang="scss">  //reset element css of el-icon-close  .tags-view-wrapper {    .tags-view-item {      .el-icon-close {        width: 16px;        height: 16px;        vertical-align: 2px;        border-radius: 50%;        text-align: center;        transition: all .3s cubic-bezier(.645, .045, .355, 1);        transform-origin: 100% 50%;        &:before {          transform: scale(.6);          display: inline-block;          vertical-align: -3px;        }        &:hover {          background-color: #b4bccc;          color: #fff;        }      }    }  }</style>

3、在store文件夹下新建一个tagsView.js,同样把复制代码进去

const tagsView = {  state: {    visitedViews: [],    cachedViews: []  },  mutations: {    ADD_VISITED_VIEWS: (state, view) => {      if (state.visitedViews.some(v => v.path === view.path)) return      state.visitedViews.push(Object.assign({}, view, {        title: view.meta.title || 'no-name'      }))      if (!view.meta.noCache) {        state.cachedViews.push(view.name)      }    },    DEL_VISITED_VIEWS: (state, view) => {      for (const [i, v] of state.visitedViews.entries()) {        if (v.path === view.path) {          state.visitedViews.splice(i, 1)          break        }      }      for (const i of state.cachedViews) {        if (i === view.name) {          const index = state.cachedViews.indexOf(i)          state.cachedViews.splice(index, 1)          break        }      }    },    DEL_OTHERS_VIEWS: (state, view) => {      for (const [i, v] of state.visitedViews.entries()) {        if (v.path === view.path) {          state.visitedViews = state.visitedViews.slice(i, i + 1)          break        }      }      for (const i of state.cachedViews) {        if (i === view.name) {          const index = state.cachedViews.indexOf(i)          state.cachedViews = state.cachedViews.slice(index, i + 1)          break        }      }    },    DEL_ALL_VIEWS: (state) => {      state.visitedViews = []      state.cachedViews = []    }  },  actions: {    addVisitedViews({ commit }, view) {      commit('ADD_VISITED_VIEWS', view)    },    delVisitedViews({ commit, state }, view) {      return new Promise((resolve) => {        commit('DEL_VISITED_VIEWS', view)        resolve([...state.visitedViews])      })    },    delOthersViews({ commit, state }, view) {      return new Promise((resolve) => {        commit('DEL_OTHERS_VIEWS', view)        resolve([...state.visitedViews])      })    },    delAllViews({ commit, state }) {      return new Promise((resolve) => {        commit('DEL_ALL_VIEWS')        resolve([...state.visitedViews])      })    }  }}export default tagsView

4、在store/index.js中引入tagsView.js

import Vue from 'vue'import Vuex from 'vuex'import getters from './getters'// 自定义模块import app from './modules/app'import settings from './modules/settings'import user from './modules/user'import permission from './modules/permission'import tagsView from './modules/tagsView'Vue.use(Vuex)const store = new Vuex.Store({  modules: {    app,    settings,    user,    permission,    tagsView  },  getters})export default store

5、在@/layout/的主组件中引入TagsView组件

二、实现重新返回页面时不刷新,保留原本页面内容

1、在@/layout/components/Sidebar下的AppMain.vue中添加如下代码

主要是通过keep-alive 和 router-view结合来实现

<template>  <section class="app-main">    <transition name="fade-transform" mode="out-in">      <keep-alive :include="cachedViews"> //这是新增加的        <router-view :key="key" />      </keep-alive> //    </transition>  </section></template><script>export default {  name: 'AppMain',  computed: {    key() {      return this.$route.path    },    cachedViews() {//这是新增加的      return this.$store.state.tagsView.cachedViews    }//  }}</script>

2、在route/index.js中为页面路由添加keepAlive为true,例如在首页

  {    path: '/',    component: Layout,    redirect: '/dashboard',    children: [{      path: 'dashboard',      name: 'Dashboard',      component: () => import('@/views/dashboard/index'),      //在meta对象中添加key值keepAlive为true      meta: { title: '首页', icon: 'dashboard' ,keepAlive:true}     }]  }

3、还有一点很重要!!要确保组件的命名和路由的命名是一样的,不然页面不会保留原本内容,会进行自动刷新

4、退出登录时,要清除缓存可以在store/modules/user.js中添加如下代码

import store from '..'// user logout  logout({ commit, state }) {    return new Promise((resolve, reject) => {      logout().then(() => {        removeToken()         resetRouter()        store.dispatch('delAllViews') //增加多这行        commit('RESET_STATE')        resolve()      }).catch(error => {        reject(error)      })    })  },

最后,这是项目的目录结构,红色框是新建的文件,蓝色是修改增加的文件

 


点击全文阅读


本文链接:http://zhangshiyu.com/post/59020.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1