注意要点:

1.每个页面组件需要置顶的name属性,与cachedViews数组里的名称一致

2.如果是多级页面缓存,一定要加上他父组件的name,比如我的是layoutEmpty

3.在main.vue和empty.vue里的router-view都需要用keep-alive包装

4.在router/index.js里每个路由都要在meta里面增加keepAlive来标识是否需要缓存

5.我这有iframe组件,所以也进行了标识,iframe缓存用的是component组件的is来实现的

这是empty.vue

<template>
  <div>
    <keep-alive :include="cachedViews">
      <router-view v-if="$route.meta.keepAlive&&!$route.meta.isIframe" :key="key"/>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive&&!$route.meta.isIframe" :key="key"/>
  </div>
</template>

<script>

export default {
  name: 'layoutEmpty',
  computed: {
    key() {
      return this.$route.path
    },
    cachedViews() {
      return this.$store.state.cachedViews
    }
  }
}
</script>

这是AppMain.vue,backTop是自己写的返回顶部组件,可以不用加

<template>
  <section class="app-main">
    <transition name="fade-transform" mode="out-in" :duration="500">
      <back-top>
        <keep-alive :include="cachedViews">
          <router-view v-if="$route.meta.keepAlive&&!$route.meta.isIfram" :key="key"/>
        </keep-alive>
        <router-view v-if="!$route.meta.keepAlive&&!$route.meta.isIfram" :key="key"/>
        <div v-for="item in hasOpenIframeComponentsArr" :key="item.name" >
          <component
              v-show="$route.name === item.name&&item.hasOpen"
              :key="item.name"
              :is="item.name"
          ></component>
        </div>
      </back-top>
    </transition>
  </section>
</template>

<script>
import BackTop from '@/components/BackTop'
import Vue from 'vue'

export default {
  name: 'AppMain',
  components: {
    BackTop,
  },
  computed: {
    key() {
      return this.$route.path
    },
    cachedViews() {
      return this.$store.getters.cachedViews
    },
    // Implement lazy loading to render only iframe pages that have been opened (hasOpen:true)
    hasOpenIframeComponentsArr() {
      return this.openedIframeComponentsArr;
    },
  },
  data() {
    return {
      iframeComponentsArr: [], // Pages with iframe
      openedIframeComponentsArr: []
    }
  },
  methods: {
    // 给当前iframe组件增加hasOpen
    isOpenIframePage() {
        const target = this.iframeComponentsArr.find(item => {
            return item.name === this.$route.name
        });
        if (target && !target.hasOpen) {
            target.hasOpen = true;
            this.openedIframeComponentsArr.push(target)
        }
    },
    // 获取iframe组件数组
    getIframeComponentsArr() {
      const router = this.$router;
      const routes = router.options.routes[1].children;
      let iframeArr = []
      for (let item of routes){
          if (item.children){
              iframeArr.push(...item.children.filter(item => item.iframeComponent))
          }
      }
      return iframeArr.map((item) => {
          const name = item.name || item.path.replace('/', '');
          return {
              name: name,
              path: item.path,
              hasOpen: false, // Whether it has been opened, default false
              component: item.iframeComponent // Reference to Component Files
          };
      });
    }
  },
  watch: {
    // 当route切换的时候执行
    $route() {
      // Determine whether the current routing is an iframe page
      this.isOpenIframePage();
    }
  },
  created() {
    // Setting the array object for the iframe page
    const iframeComponentsArr = this.getIframeComponentsArr();
    iframeComponentsArr.forEach((item) => {
        Vue.component(item.name, item.component);
    });
    this.iframeComponentsArr = iframeComponentsArr;
    // Determine whether the current routing is an iframe page
    this.isOpenIframePage();
  },
}
</script>

<style lang='scss' scoped>
.app-main {
  // min-height: calc(100vh - 50px);
  height: 100%;
  width: 100%;
  position: relative;
  overflow: auto;
}
.fixed-header+.app-main {
  padding-top: 84px;
}
</style>

<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
  .fixed-header {
    padding-right: 15px;
  }
}
</style>

在store/index.js里需要初始化定义一下

const state = sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')) : {
  visitedViews: [],
  cachedViews: ["layoutEmpty"],
  activeIndex: ''
}

这是store/mutations.js

// 添加tab
const add_visited_view = (state, view) => {
    var tab_data = {path: view.path, title: view.meta.title, closable: view.meta.closable, name: view.name}
    if (state.visitedViews.some(v => v.path === view.path)) return
    state.visitedViews.push(tab_data);
}

// 添加缓存的页面
const add_cached_view = (state, view) => {
    if (state.cachedViews.includes(view.name)) return
    if (view.meta.keepAlive) {
        state.cachedViews.push(view.name);
    }
}

// 删除tabs里的tab
const delete_visited_view = (state, view) => {
    for (const [i, v] of state.visitedViews.entries()) {
        if (v.path === view.path) {
           state.visitedViews.splice(i, 1)
           break
        }
    }
}

// 删除缓存的页面
const delete_cached_view = (state, view) => {
    const index = state.cachedViews.indexOf(view.name)
    index > -1 && state.cachedViews.splice(index, 1)
}

export default {
    // 添加tabs
    add_view (state, view) {
        add_visited_view(state, view);
        add_cached_view (state, view);
    },

    // 删除tabs
    delete_view (state, view) {
        delete_visited_view(state, view);
        delete_cached_view(state, view);
    },

    // 设置当前激活的tab
    set_active_tab (state, index) {
        state.activeIndex = index;
    }

}

这是store/getter.js

const getters = {
    activeIndex: state => state.activeIndex,
    visitedViews: state => state.visitedViews,
    cachedViews: state => state.cachedViews
  }

export default getters

这是tags.vue

<template>
    <div>
      <el-row>
        <el-col :span="24">
          <el-tabs v-model="activeIndex" type="card" @tab-remove="removeTab" @tab-click="tabClick">
              <el-tab-pane
                v-for="item in visitedViews"
                :key="item.title"
                :label="item.title"
                :name="item.path"
                :closable="item.closable"
              >
              </el-tab-pane>
          </el-tabs>
        </el-col>
      </el-row>
    </div>
</template>

<script>
import { generateTitle } from '@/utils/i18n'

export default {
  name: 'TabsNav',
  props: { },
  data () {
    return {
    }
  },
  created () {
    this.$nextTick(()=> {
      this.activeIndex = this.$route.path;
    });
  },
  watch: {
    $route:{
      handler(to, from){
        this.addTabs();
        this.moveToCurrentTag();
      },
      deep: true
    }
  },
  mounted () {
    // 刷新时以当前路由做为tab加入tabs
    // 当前路由不是首页时,添加首页以及另一页到store里,并设置激活状态
    // 当前路由是首页时,添加首页到store,并设置激活状态
    this.initTabs()
    this.addTabs()
  },
  computed: {
    visitedViews () {
      return this.$store.getters.visitedViews
    },
    activeIndex: {
      get () {
        return this.$store.getters.activeIndex
      },
      set (val) {
        this.$store.commit('set_active_tab', val)
      }
    }
  },
  methods: {
    generateTitle,
    //删除tab
    removeTab(target) {
      if ( target == '/' || target == '/home' ){
        return
      }
      this.$store.commit('delete_view', this.$route)
      if (this.activeIndex === target) {
        // 设置当前激活的路由
        if (this.visitedViews && this.visitedViews.length >= 1) {
          this.$store.commit('set_active_tab', this.visitedViews[this.visitedViews.length - 1].path)
          this.$router.push({path: this.activeIndex})
        }
      }
    },
    //tab点击
    tabClick(tab){
      this.$router.push({path: tab.name})
    },
    // 添加tab
    addTabs() {
      const { name } = this.$route
      if (name) {
        this.$route.meta.title = this.generateTitle(this.$route.meta.title)
        this.$route.meta.closable = true
        this.$store.commit('add_view', this.$route)
      }
      return false
    },
    // 切换活动页
    moveToCurrentTag() {
      this.$store.commit('set_active_tab', this.$route.path)
    },
    // 初始化tabs
    initTabs(){
      var flag = this.visitedViews.some(item=>{
        if(item.name == 'Home'){
          return true
        }
      })
      if (!flag){
        const data = {
          path: '/home',
          name: 'Home',
          meta: {
            title: this.generateTitle('home'),
            closable: false,
            keepAlive: false
          }
        }
        this.$store.commit('add_view', data)
      }
      this.$store.commit('set_active_tab', this.$route.path)
    }
  },
}
</script>

最终使用的是在index.vue里

<el-container>
    <el-header>           
        <div>
            <tags />
        </div>
    </el-header>
    <el-main class="main-container" style="background: white;">
        <app-main />
    </el-main>
</el-container>

参考资料

vue elementUI使用tabs与导航栏联动-面圈网

keep alive (no refresh) for iframe in Vue

vue3使用 keep-alive对iframe进行缓存_叫我多多酱的博客-CSDN博客_vue 缓存iframe

vue移动端项目缓存问题实践_Android开发者小P的博客-CSDN博客

https://www.jb51.net/article/205539.htm

vue组件缓存之keep-alive正确使用姿势 - 知乎

Vue keep-alive 多级页面不缓存问题_始于颜值 陷于才华 忠于人品的博客-CSDN博客_keep-alive 不缓存

Logo

前往低代码交流专区

更多推荐