vue-router路由后退实现关闭页内弹窗 画面不退出、不回退

问题描述

在项目开发中使用vue-router,经常会遇到使用Modal弹窗的场景,然后点击后退不是Modal关闭,而是页面后退的问题。

比如在移动端,一个画面业内弹出一个Modal,为了关闭Modal,我们习惯性的会按手机回退键,但这时候会发现页面返回到了上一个路由。

解决方案

为了解决这个问题,我们可以这么设计:

在页面中埋一个router-view,即添加一个临时用的子路由。子路由呈现空的状态,所以我们看不到这个临时路由的任何元素。

页面中嵌套router-view

下面是路由配置

2019-11-09-18-32-21.png

我们点击显示modal的时,让路由进入子路由。

this.$router.push({name: 'child-place-holder'})

然后在Modal点击关闭按钮的时候,同时调用路由后退,用来实现关闭modal,且关闭子路由,以此来保证路由的正确性了。

this.$router.back();

通过这个解决方案我们解决了:

  • 点击后退按钮,会关闭子路由画面,不会关闭Modal所在的画面。
  • 点击Modal关闭按钮,正确关闭了Modal和保证了路由的正确。

新的问题

那么问题来了,我们点击了Modal,把Modal显示出来,并进入了子路由,我们按后退键,关闭了子路由,但是Modal还没关掉,这和我们的预期不符。

为了解决这个问题,我先是尝试在vue-router的beforeRouteEnter方法中关闭Modal。

{
    data: {
        modal_visible: true
    },

    beforeRouteEnter(to, from, next){
        ...
        this.modal_visble = false
    }
}

但是,我发现,关闭子路由并不会触发beforeRouteEnter事件。 所以这个方案不行。

再次解决问题

通过研究发现,我们发现,通过监控$router,关闭子路由时会触发。我们以这个为出发点寻找解决方案。

export default {

    watch: {
        $route(to, from) {
        },
    },
}

$router会有两个参数: to和from。 因为这段代码是写在Modal所在画面中,所以,to肯定是指Modal所在的路由,from则是子路由。

路由每次变化,上面的代码都会执行,比如从Modal所在的父级页面进入Modal页面,上面代码也会执行。这样的话,我们需要判断的是,这变化是不是从子路由转过来的。

to和from两个路由的信息可以说是非常贫瘠了,不太够用,为了判断路由跳转是否来自子路由,我们需要利用路由配置,好在我们可以通过下面的代码来获取路由配置信息。

export default {

    watch: {
        $route(to, from) {
        },
    },

    computed: {
        // 路由配置
        routes() {
            return this.$router.options.routes;
        },
    },
};

第一步,通过遍历路由配置,找到当前路由的配置。

export default {

    watch: {
        $route(to, from) {

            // 找到当前的路由配置
            const current_route_option = this.find_router_by_name(this.$route.name);
        },
    },

    computed: {
        // 路由配置
        routes() {
            return this.$router.options.routes;
        },
    },

    methods: {

        // 根据路由名称查找路由
        find_router_by_name(name, routes) {
            routes = routes || this.routes || [];
            for (var index = 0; index < routes.length; index++) {
                const route = routes[index];
                if (route.name === name) return route;

                if (route.children && route.children.length) {
                    const findResult = this.find_router_by_name(name, route.children);
                    if (findResult) return findResult;
                }
            }
        },
    },
};

第二步,判断from路由是否是当前路由的子路由

根据from的name,在当前路由的children数组中尝试找到相等的name。如果找到,则是来自子路由,否则不是。

export default {
    watch: {
        $route(to, from) {

            // 找到当前的路由配置
            const current_route_option = this.find_router_by_name(this.$route.name);
            const is_from_children = this.is_from_children(from, current_route_option);
        },
    },

    computed: {
        // 路由配置
        routes() {
            return this.$router.options.routes;
        },
    },

    methods: {
        // 根据路由名称查找路由
        find_router_by_name(name, routes) {
            routes = routes || this.routes || [];
            for (var index = 0; index < routes.length; index++) {
                const route = routes[index];
                if (route.name === name) return route;

                if (route.children && route.children.length) {
                    const findResult = this.find_router_by_name(name, route.children);
                    if (findResult) return findResult;
                }
            }
        },

        // 判断是否来自(亲)子路由的转过来的
        is_from_children(from_route, current_route_options) {
            if (!from_route) return false;
            if (!current_route_options.children || !current_route_options.children.length) return false;

            for (let index = 0; index < current_route_options.children.length; index++) {
                const child = current_route_options.children[index];
                console.log(child.name, from_route.name);
                if (child.name === from_route.name) return true;
            }

            return false;
        },
    },
};

第三步,如果是来自子路由,调用指定方法

通过上面的步骤,我们可以判断,路由跳转是否来自子路由,接下来我们要做的就是,如果来自子路由,我们同意执行指定的方法back_from_children,并把form的路由名称当做参数传入。

export default {
    watch: {
        $route(to, from) {

            // 找到当前的路由配置
            const current_route_option = this.find_router_by_name(this.$route.name);
            const is_from_children = this.is_from_children(from, current_route_option);
            if (is_from_children) this.back_from_children(from.name);
        },
    },

    computed: {
        // 路由配置
        routes() {
            return this.$router.options.routes;
        },
    },

    methods: {

        // 当从子路由进来的时候,会调用这个方法
        // from_route_name子路由名字
        back_from_children(from_route_name) {
            // 重写这个方法
        },

        // 根据路由名称查找路由
        find_router_by_name(name, routes) {
            routes = routes || this.routes || [];
            for (var index = 0; index < routes.length; index++) {
                const route = routes[index];
                if (route.name === name) return route;

                if (route.children && route.children.length) {
                    const findResult = this.find_router_by_name(name, route.children);
                    if (findResult) return findResult;
                }
            }
        },

        // 判断是否来自(亲)子路由的转过来的
        is_from_children(from_route, current_route_options) {
            if (!from_route) return false;
            if (!current_route_options.children || !current_route_options.children.length) return false;

            for (let index = 0; index < current_route_options.children.length; index++) {
                const child = current_route_options.children[index];
                console.log(child.name, from_route.name);
                if (child.name === from_route.name) return true;
            }

            return false;
        },
    },
};

最后的使用

上面的代码,我们保存为RouterMixin.js

然后在我们的Modal界面引入使用:

引入mixin

然后我们重写我们的back_from_children方法:

重写方法

里面的逻辑就很清晰了,如果来自child-place-holder路由,那么就可以关掉modal了,其他地方来的,则不做关闭操作。

gif图片效果预览

预览

动图中注意url地址栏的变化

还没解决的问题

如果直接访问临时路由的url地址,要点两次后退才能退出Modal所在画面。

总结

这个解决方案,利用了vue的mixin机制。

在Modal所在的画面,路由变化时,都要遍历路由配置这棵树,对大一点规模的应用可能会有写性能影响。

使用时,耦合度相对较高,要记住自己的临时路由的名字,记住定义的方法名进行重写等。

写好mixin文件后,工作量也还能接受。

考虑不周的地方,欢迎大家指正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容