0
点赞
收藏
分享

微信扫一扫

VUE 单页应用多页标签显示组件及路由组件缓存设计 (升级后)

JakietYu 2022-02-16 阅读 109

单页应用多页标签组件及组件缓存设计

优化后解决的问题

  • 解决相同name组件路由打开多标签页后,关闭一个时会把其他相同name组件缓存都清除的问题;
  • 解决相同name组件路由打开多标签页后,标签页内容有相同的时候出现的异常问题(比如订单详情,两个订单款号相同时,标签头显示相同就会出现问题);
  • 解决某些情况下缓存组件没能在内存中清除,导致内存占用的问题;
  • 增加了标签可以鼠标右击操作菜单功能,可以关闭当前点击的标签左边或者右边的标签页

路由配置项约定

  • 所有路由都会缓存,并且都会添加标签头,目前不考虑(部门要缓存,还有部分不要缓存的需求);

  • 如果某个路由需要可同时开多标签页的,要配置路由的 meta.canMultipleOpen = true,比如用户详情页面路由是需要可以多开的;

syPageTabs.vue

这是路由标签组件,用来显示当前打开的路由标签,并可以操作标签页,支持切换标签页组件显示,关闭标签页,重载标签页组件

<template>
    <div class="nav-click ">
        <ul >
            <li class="nav-click-body" v-for="(item,index) in openedPageRouters" :key="item.fullPath"
                @contextmenu.prevent="showContextMenu($event, item, index)"
                :title="item.meta.title + (item.query.flag || '')"
                :class="{'nav-click-show':item.fullPath == $route.fullPath,'nav-click-default':!(item.fullPath == $route.fullPath)}">
                <div class="nav-click-body-title hidden" @click="onClick(item)">
                    {{item.meta.title}} {{(item.query.flag || '')}}
                </div>
                <div v-if="index!=0" class="nav-click-body-close" @click="onClose(item)"
                    :class="{'nav-click-body-show':item.fullPath == $route.fullPath,'nav-click-body-default':!(item.fullPath == $route.fullPath)}">
                    ×</div>
            </li>
        </ul>
        <div v-show="contextMenuVisible" style="position: fixed;"
            :style="{ 'left': contextMenuLeft + 'px', 'top': contextMenuTop + 'px' }">
            <ul class="__contextmenu">
                <li>
                    <el-button type="text" @click="reloadRoute" size="mini">重新加载</el-button>
                </li>
                <li>
                    <el-button type="text" @click="closeOtherLeft" size="mini">关闭左边</el-button>
                </li>
                <li>
                    <el-button type="text" @click="closeOtherRight" size="mini">关闭右边</el-button>
                </li>
                <li>
                    <el-button type="text" @click="closeOther" size="mini">关闭其他</el-button>
                </li>
            </ul>
        </div>
    </div>
</template>
<script>
    export default {
        name: 'syPageTabs',
        props: {
            keepAliveComponentInstance: {}, //keep-alive控件实例对象
            blankRouteName: {
                type: String,
                default: "blank",
            }, //空白路由的name值
            firstPageRouter: Object, // 系统首页路由对象
        },
        watch: {
            //当路由变更时,执行打开页面的方法
            $route: {
                handler(v) {
                    this.openPage(v);
                },
                immediate: true,
            },
        },
        data() {
            return {
                openedPageRouters: [], //已打开的路由页面

                contextMenuVisible: false, //右键菜单是否显示
                contextMenuLeft: 0, //右键菜单显示位置
                contextMenuTop: 0, //右键菜单显示位置
                contextMenuTargetPageRoute: null, //右键所指向的菜单路由
            }
        },

        mounted() {
            //添加点击关闭右键菜单
            window.addEventListener("click", this.closeContextMenu);
        },
        destroyed() {
            window.removeEventListener("click", this.closeContextMenu);
        },
        methods: {
            //右键显示菜单
            showContextMenu(e, route, index) {
                if (index == 0) {
                    return
                }
                this.contextMenuTargetPageRoute = route;
                this.contextMenuLeft = e.screenX //e.layerX;
                this.contextMenuTop = e.clientY //e.layerY;
                this.contextMenuVisible = true;
            },
            //隐藏右键菜单
            closeContextMenu() {
                this.contextMenuVisible = false;
                this.contextMenuTargetPageRoute = null;
            },


            // 插入首页
            insertFirstPage(route) {
                if (this.firstPageRouter && route.path !== this.firstPageRouter.path) {
                    this.openedPageRouters.push(this.firstPageRouter)
                }
            },

            //打开页面
            openPage(route) {
                // 如果还没创建过一个标签页,先将首页添加标签
                if (this.openedPageRouters.length < 1) {
                    this.insertFirstPage(route);
                }

                if (route.name == this.blankRouteName) {
                    return;
                }
                // 检查该标签页是否已创建
                let isExist = this.openedPageRouters.some(
                    (item) => item.fullPath == route.fullPath
                );

                // 如果没创建,并且路径不等于根路径
                if (!isExist && route.fullPath !== "/") {
                    let openedPageRoute = this.openedPageRouters.find(
                        (item) => item.path == route.path // 注意这边找的不是全路径
                    );
                    //判断页面是否支持不同参数多开页面功能,如果不支持且已存在path值一样的页面路由,那就替换它
                    if (!route.meta.canMultipleOpen && openedPageRoute != null) {
                        this.delRouteCache(openedPageRoute.fullPath);
                        this.openedPageRouters.splice(this.openedPageRouters.indexOf(openedPageRoute), 1, route);
                    } else {
                        this.openedPageRouters.push(route);
                    }
                }
            },

            // 关闭所有的标签
            closeAllClick() {
                // 如果设置了系统首页标签,首页标签不能关掉
                if (this.firstPageRouter && this.openedPageRouters.length < 2) return;

                for (let i = 1; i < this.openedPageRouters.length; i++) {
                    this.delPageRoute(this.openedPageRouters[i]);
                }
                // 全部关闭后,路由跳到首页
                this.onClick(this.openedPageRouters[0]);
            },

            //点击页面标签卡时
            onClick(route) {
                if (route.fullPath !== this.$route.fullPath) {
                    this.$router.push(route.fullPath);
                }
            },
            //关闭页面标签时
            onClose(route) {
                let index = this.openedPageRouters.indexOf(route);
                this.delPageRoute(route);
                if (route.fullPath === this.$route.fullPath) {
                    //删除页面后,跳转到上一页面
                    this.$router.replace(
                        this.openedPageRouters[index == 0 ? 0 : index - 1]
                    );
                }
            },

            //删除页面
            delPageRoute(route) {
                let routeIndex = this.openedPageRouters.indexOf(route);
                if (routeIndex >= 0) {
                    this.openedPageRouters.splice(routeIndex, 1);
                }
                this.delRouteCache(route.fullPath);
            },

            //删除页面缓存
            delRouteCache(key) {
                let cache = this.keepAliveComponentInstance.cache;
                let keys = this.keepAliveComponentInstance.keys;
                for (let i = 0; i < keys.length; i++) {
                    if (keys[i] == key) {
                        keys.splice(i, 1);
                        if (cache[key] != null) {
                            cache[key].componentInstance.$destroy()
                            delete cache[key];
                        }
                        break;
                    }
                }
            },

            //关闭右边页面
            closeOtherRight() {
                let index = this.openedPageRouters.indexOf(
                    this.contextMenuTargetPageRoute
                );
                let currentIndex = this.getPageRouteIndex(this.$route.fullPath);
                for (let i = index + 1; i < this.openedPageRouters.length; i++) {
                    let r = this.openedPageRouters[i];
                    this.delPageRoute(r);
                    i--;
                }
                if (index < currentIndex) {
                    this.$router.replace(this.contextMenuTargetPageRoute);
                }
            },
            //关闭左边页面
            closeOtherLeft() {
                let index = this.openedPageRouters.indexOf(
                    this.contextMenuTargetPageRoute
                );
                let currentIndex = this.getPageRouteIndex(this.$route.fullPath);
                if (index > currentIndex) {
                    this.$router.replace(this.contextMenuTargetPageRoute);
                }
                let startIndex = this.firstPageRouter ? 1 : 0;
                for (let i = startIndex; i < index; i++) {
                    let r = this.openedPageRouters[i];
                    this.delPageRoute(r);
                    i--;
                    index--;
                }
            },
            //关闭其他页面
            closeOther() {
                let startIndex = this.firstPageRouter ? 1 : 0;
                for (let i = startIndex; i < this.openedPageRouters.length; i++) {
                    let r = this.openedPageRouters[i];
                    if (r !== this.contextMenuTargetPageRoute) {
                        this.delPageRoute(r);
                        i--;
                    }
                }
                if (this.contextMenuTargetPageRoute.fullPath != this.$route.fullPath) {
                    this.$router.replace(this.contextMenuTargetPageRoute);
                }
            },

            // 重载指定标签页
            reloadRoute() {
                // 先销毁缓存
                this.delRouteCache(this.contextMenuTargetPageRoute.fullPath);
                // 跳到空页面,再跳回原路由页面
                this.$router.replace({
                    name: this.blankRouteName
                }).then(() => {
                    this.$router.replace(this.contextMenuTargetPageRoute);
                });
            },

            //重载页面
            reload() {
                // 先找到当前路由标签对象
                let i = this.getPageRouteIndex(this.$route.fullPath);
                let curr_router = this.openedPageRouters[i];

                // 先销毁缓存
                this.delRouteCache(curr_router.fullPath);
                // 跳到空页面,再跳回原路由页面
                this.$router.replace({
                    name: this.blankRouteName
                }).then(() => {
                    this.$router.replace(curr_router);
                });
            },

            //根据路径获取索引
            getPageRouteIndex(fullPath) {
                for (let i = 0; i < this.openedPageRouters.length; i++) {
                    if (this.openedPageRouters[i].fullPath === fullPath) {
                        return i;
                    }
                }
            },
        }

    }
</script>

App.vue

将对标签及缓存组件的操作全部标签组件(syPageTabs)内;

<template>
    <div>
        <div v-if="$store.state.hasLogin == true">

            <!-- 头部 -->
            <sy-Head @on-refrash="reload"></sy-Head>

            <!-- 页标签显示栏 -->
            <syPageTabs  v-show="$route.meta.showClick" ref="tabs" :keep-alive-component-instance="keepAliveComponentInstance"
                :firstPageRouter="firstPage"></syPageTabs>

            <!-- 导航 -->
            <sy-Nav @showPage="showPage"></sy-Nav>

            <div ref="keepAliveContainer">
                <!-- 页面主体内容 -->
                <keep-alive>
                    <router-view :key="$route.fullPath"></router-view>
                </keep-alive>
            </div>


        </div>

        <!-- 全局显示的遮罩层 -->
        <div v-if="$store.state.TMP.request_count>0 && $store.state.TMP.loadingBar==true">
            <div
                style="position: fixed; top: 0px; left: 0px; right: 0px; bottom: 0px; z-index: 99999; background: #eee; opacity: 0.5;">
            </div>
            <div
                style="font-size:50px; text-align:center; position: fixed; top: 0px; left: 0px; right: 0px; bottom: 0px; z-index: 99999;">
                <i class="fa fa-spinner fa-spin" style="margin-top:20%;"></i>
            </div>
        </div>
    </div>
</template>

<script>
    import syHead from './pages/syHead.vue'
    import syNav from './pages/syNav.vue'
    import home from './pages/home.vue'
    import syPageTabs from './pages/syPageTabs.vue'
    import Cookies from 'js-cookie'

    var _self;

    export default {
        name: 'app',
        components: {
            home,
            syNav,
            syHead,
            syPageTabs
        },
        data() {
            return {
                // 用来控制应用加载时页面延迟显示
                showApp: false,

                // 键盘control是否被按下
                isUpKey_Control: false,
                websocketUrl: '',
                websocketModel: false,

                // keep-alive的控件实例对象
                keepAliveComponentInstance: null,
                // 固定的系统首页路由对象
                firstPage: {
                    "name": "home",
                    "meta": {
                        "title": "系统首页",
                        "requireAuth": false,
                        "showHeader": true,
                        "showNav": true,
                        "showtag": true,
                        "showPosition": true,
                    },
                    "path": "/home",
                    "query": {},
                    "params": {},
                    "fullPath": "/home"
                },
                noExecGetCss: 0,
            }
        },
        created() {
            _self = this;
        },
        mounted: function() {
            // 得到浏览器内框高度
            window.onresize = function() {
                if (_self.noExecGetCss == 0){
                    setTimeout(() => {
                        _self.$store.commit('getCss');
                        _self.noExecGetCss = 0;
                    }, 500)
                }
                _self.noExecGetCss++;
            }
            window.onresize();

            /**************** 下面两个键盘监控是用来控制F5刷新组件,Control+F5刷新浏览器  ************/
            // 监控键盘按下事件
            document.onkeydown = function(e) {
                let keyNum = window.event ? e.keyCode : e.which; //获取被按下的键值 
                // 键盘按下control键时
                if (keyNum == 17) {
                    _self.isUpKey_Control = true;
                }
                // 键盘按下F5键时
                if (keyNum == 116) {
                    if (_self.isUpKey_Control == false) {
                        // 刷新当前路由组件
                        _self.reload()
                        // 阻止浏览器刷新
                        return false
                    }
                }
            }
            // 监控键盘按键抬起事件
            document.onkeyup = function(e) {
                let keycode = window.event ? e.keyCode : e.which;
                // 键盘按下control键抬起后
                if (keycode == 17) {
                    _self.isUpKey_Control = false;
                }
            }
        },
        methods: {
            // 导航菜单点击
            showPage: function(router) {
                this.$router.push(router);
            },

            // 刷新当前标签页组件
            reload() {
                this.$refs.tabs.reload();
            },

            // 获取keep-alive的控件实例对象传入标签组件
            getKeepAliveContainer() {
                // 获取keep-alive的控件实例对象
                this.$nextTick(() => {
                    if (this.$refs.keepAliveContainer) {
                        this.keepAliveComponentInstance = this.$refs.keepAliveContainer.childNodes[0].__vue__;
                    }
                })
            }
        },
        watch: {
            '$store.state.hasLogin': {
                handler(val, oldVal) {
                    if (val == true) {
                        // 因为当还没登录时,App组件里的子组件元素都还没创建,所以this.$refs.keepAliveContainer是Null,所以要放在登录后获取
                        this.getKeepAliveContainer();

                    }
                },
                immediate: true
            }
        }
    }
</script>

router/index.js

路由配置中增加一个空白路由

//这个是空白页面,重新加载当前页面会用到
    {
      name: "blank",
      path: "/blank",
    }
举报

相关推荐

0 条评论