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文件后,工作量也还能接受。

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

你可能感兴趣的:(vue-router路由后退实现关闭页内弹窗 画面不退出、不回退)