vue2基于vuex和keep-alive实现tab切换和页面缓存

818 阅读1分钟

前言

本文主要实现tab切换并缓存对应页面,因为系统包含使用iframe嵌套的跨系统页面,而使用keep-alive缓存的iframe页面是不生效的,所以最终主要采用keep-alive缓存当前系统的页面及使用iframe的显示隐藏缓存跨系统页面

一、tab切换实现

<template>
    <div class="tags-view">
        <el-tabs v-model="activeTab" type="card" @tab-remove="handleClose" @tab-click="handleClick"
                 @contextmenu.prevent.native="openContextMenu($event)">
            <el-tab-pane
                v-for="(item, index) in visitedViews"
                :key="item.randomVal"
                :label="item.title"
                :name="item.path"
                :closable="index > 0 ? true : false"
                lazy>
            </el-tab-pane>
        </el-tabs>
        <ul
            v-show="contextMenuVisible"
            :style="{left: menuLeft+'px',top: menuTop+'px'}"
            class="contextmenu">
            <li @click="handleClose(openMenuTag.path)">关闭</li>
            <li @click="closeLeftTabs">关闭左边</li>
            <li @click="closeRightTabs">关闭右边</li>
            <li @click="closeOtherTabs">关闭其他</li>
            <li @click="closeAllTabs">关闭所有</li>
        </ul>
    </div>
</template>
<script>
import {mapMutations, mapState} from "vuex";

export default {
    name: 'tagsView',
    data() {
        return {
            // 当前激活的菜单
            activeTab: '',
            // 是否显示右键菜单
            contextMenuVisible: false,
            menuLeft: 0,
            menuTop: 0,
            // 右键菜单对应的当前tag
            openMenuTag: {}
        }
    },
    computed: {
        ...mapState({
            visitedViews: state => state.tagsView.visitedViews,
        })
    },
    watch: {
        $route: {
            handler (val) {
                this.activeTab = val.path;
                // 添加路由tab
                this.ADD_VIEW({
                    ...val
                });
            },
            immediate: true
        },
        contextMenuVisible() {
            if (this.contextMenuVisible) {
                document.body.addEventListener("click", this.closeContextMenu);
            } else {
                document.body.removeEventListener("click", this.closeContextMenu);
            }
        }
    },
    methods: {
        ...mapMutations('tagsView', [
            "ADD_VIEW",
            "DEL_VIEW",
            "DEL_LEFT_VIEW",
            "DEL_RIGHT_VIEW",
            "DEL_OTHER_VIEW",
            "DEL_ALL_VIEW",
        ]),
        // 添加路由
        handleClick(tab) {
            this.$router.push(this.visitedViews[tab.index].fullPath)
        },
        // 右键显示菜单
        openContextMenu(e) {
            if (e.target.id) {
                // 获取当前tag对应的路由路径
                const path = e.target.id.split("-")[1];
                // 设置菜单位置
                this.menuLeft = e.pageX;
                this.menuTop = e.pageY + 10;
                this.contextMenuVisible = true;
                // 设置当前菜单对应的tag对象
                const index = this.visitedViews.findIndex(item => {
                    return item.path === path;
                });
                this.openMenuTag = {...this.visitedViews[index], tagIndex: index};
            }
        },
        // 移除当前tab
        handleClose(routePath) {
            // 移除当前tab
            this.DEL_VIEW(routePath);
            // 默认显示最后一个tab
            this.moveToLastTag();
        },
        // 关闭左侧tabs
        closeLeftTabs() {
            // 关闭左侧tabs
            this.DEL_LEFT_VIEW(this.openMenuTag.tagIndex);
            // 默认显示最后一个tab
            this.moveToLastTag();
        },
        // 关闭右侧tabs
        closeRightTabs() {
            // 关闭右侧tabs
            this.DEL_RIGHT_VIEW(this.openMenuTag.tagIndex);
            // 默认显示最后一个tab
            this.moveToLastTag();
        },
        // 关闭其他tabs
        closeOtherTabs() {
            // 关闭其他tabs
            this.DEL_OTHER_VIEW(this.openMenuTag.tagIndex);
            // 默认显示最后一个tab
            this.moveToLastTag();
        },
        // 关闭所有tabs
        closeAllTabs() {
            // 关闭所有tabs
            this.DEL_ALL_VIEW();
            // 默认显示最后一个tab
            this.moveToLastTag();
        },
        // 关闭contextMenu
        closeContextMenu() {
            this.contextMenuVisible = false;
        },
        // 默认显示最后一个tab
        moveToLastTag() {
            // 获取最后一个tab对象
            const lastTag = this.visitedViews[this.visitedViews.length - 1];
            if (this.$route.path === lastTag.path) {
                return;
            }
            this.$router.push(lastTag.fullPath);
        },
    }
}
</script>

二、vuex状态存储tab列表

const state = {
    visitedViews: [
        {
            fullPath: '/',
            path: '/',
            title: '首页',
            randomVal: Math.random()*10000
        }
    ]
};

const mutations = {
    // 添加视图
    ADD_VIEW (state, view) {
        const viewObj = Object.assign({}, view, {
            title: view.meta.title || view.meta.childTitle || 'no-name',
            randomVal: Math.random()*10000
        });
        const index = state.visitedViews.findIndex(v => {
            // 此处因为我们路由层级默认是2级,2级以上的基本都是参数,例如:/system/test/:id
            const v_path = v.path.split("/").splice(0,3).join("/");
            const view_path = view.path.split("/").splice(0,3).join("/");
            return v_path.indexOf(view_path) !== -1
        });
        if (index !== -1) { // 编辑
            this._vm.$set(state.visitedViews,index,viewObj);
        } else { // 新增
            state.visitedViews.push(viewObj)
        }
    },
    // 移除视图
    DEL_VIEW (state, routePath) {
        const index = state.visitedViews.findIndex(item => item.path === routePath);
        state.visitedViews.splice(index, 1);
    },
    // 移除左侧视图
    DEL_LEFT_VIEW (state, tabIndex) {
        state.visitedViews = state.visitedViews.filter((item, index) => {
            return index === 0 || index >= tabIndex
        });
    },
    // 移除右侧视图
    DEL_RIGHT_VIEW (state, tabIndex) {
        state.visitedViews = state.visitedViews.filter((item, index) => {
            return index === 0 || index <= tabIndex
        });
    },
    // 移除右侧视图
    DEL_OTHER_VIEW (state, tabIndex) {
        state.visitedViews = state.visitedViews.filter((item, index) => {
            return index === 0 || index === tabIndex
        });
    },
    // 移除所有视图
    DEL_ALL_VIEW (state) {
        state.visitedViews = state.visitedViews.filter((item, index) => {
            return index === 0
        });
    },
    // 重置视图
    RESETVIEWS (state) {
        state.visitedViews = [
            {
                fullPath: '/',
                path: '/',
                title: '首页',
                randomVal: Math.random()*10000
            }
        ]
    }
};

const actions = {

};

export default {
    namespaced: true, // 开启命名空间
    state,
    mutations,
    actions
}

三、缓存

<!-- 需要缓存的iframe页面 通过v-show控制显示隐藏显示条件根据自己业务逻辑进行修改-->
<template v-for="cView in cachedIframeViews">
    <transition name="fade-transform" mode="out-in" :key="cView.rule_id">
        <iframe
            :src="cView.iframeUrl"
            frameborder="0"
            class="main-iframe"
            v-show="curRouteMenu.rule_id === cView.rule_id">
        </iframe>
    </transition>
</template>

<!-- 不需要缓存的iframe页面 通过v-if控制 -->
<transition name="fade-transform" mode="out-in">
    <iframe :src="currentUrl" frameborder="0" class="main-iframe" v-if="!curRouteMenu.isCache"></iframe>
</transition>

<!-- keep-alive 是否需要缓存通过include控制 cachedNames是需要缓存的组件name -->
<transition name="fade-transform" mode="out-in">
    <keep-alive :include="cachedNames">
        <router-view></router-view>
    </keep-alive>
</transition>

四、结果

image.png