Vue实现动态路由及登录&404页面跳转控制&页面刷新空白解决方案

简介: Vue实现动态路由及登录&404页面跳转控制&页面刷新空白解决方案

Vue实现动态路由及登录&404页面跳转控制&页面刷新空白解决方案

 

开发环境

 

Win 10

 

Vue 2.9.6

 

node-v10.15.3-x64.msi

下载地址:

https://nodejs.org/en/

 

代码片段(router/index.js)

说明:代码中动态路由的获取是通过解析菜单资源获取的

import Vue from "vue";

import Router from "vue-router";

 

import store from "@/store";

import Index from "@/views/Index";

import api from "@/common/network/api";

import Login from "@/views/Login";

import NotFound from "@/views/Error/404";

 

import Cookies from "js-cookie";

 

Vue.use(Router);

 

// 静态路由

conststaticRoute = [

 {

   path: "/",

   name: "Index",

   component: Index

 },

 {

   path: "/login",

   name: "登录",

   component: Login

 },

 {

   path: "/error/404",

   name: "notFound",

   component: NotFound

 }

];

 

const router = new Router({

 mode: "history", //  去掉 http://localhost:8080/#的#

 routes: staticRoute

});

 

/*vue是单页应用,刷新时,重新创建实例,需要重新加载的动态路由,不然匹配不到路由,出现页面空白的情况*/

router.beforeEach((to, from, next) => {

 // let userId = sessionStorage.getItem("userId") // 登录界面登录成功之后,会把用户信息保存在会话 // 关闭浏览器tab标签页,重新打开一个tab页,重新访问该站点,这时会开启一个新的会话,原先登录后保存的userId丢失

 let token = Cookies.get("token"); // 仅登录情况才存在token

 if (to.path === "/login") {

   // 如果是访问登录界面,如果token存在,代表已登录过,跳转到主页

   if (token) {

     next({ path: "/" });

   } else {

     // 否则,跳转到登录页面

     next();

   }

 } else {

   if (to.meta.requireAuth) {

     // 如果访问非登录界面,且路由需要登录

     if (!token) {

       // 用户token不存在,代表未登录,跳转登录

       next({

         path: "/login",

         query: { redirect: to.fullPath } // 把要跳转的路由path作为参数,登录成功后跳转到该路由

       });

     } else {

       // 用户已登录,添加动态菜单和路由后直接跳转

       addDynamicMenuAndRoutes(to, from, next);

       // 注释掉一下代码是addDynamicMenuAndRoutes函数中axios异步请求获取菜单,请求还没返回结果就开始执行next()函数,这样会导致重复请求菜单资源,特别是登录的时候,会发送两次请求,解决方案就是把以下注释掉的代码放到动态添加菜单和路由方法里执行

       //next()

 

       //if (to.matched.length == 0) {

       //  router.push(to.path)

       //}

     }

   } else {

     // 不需要登录,添加动态菜单和路由后,直接跳转

     addDynamicMenuAndRoutes(to, from, next);

   }

 }

});

 

/**

* 加载动态菜单和路由

*/

function addDynamicMenuAndRoutes(userName, to, from, next) {

 if (store.state.app.menuRouteLoaded) {

   console.log("动态菜单和路由已经存在.");

   next();

   return;

 }

 

 //优先从本地sessionStorage获取

 let navMenuData = sessionStorage.getItem("navMenuData");

 if (navMenuData) {

   navMenuData = JSON.parse(navMenuData);

   // 获取动态路由

   let dynamicRoutes = getDynamicRoutes(navMenuData);

 

   // 设置获取的路由全部为根路由(path值为 "/")下的子路由

   // 这里,根据静态路由配置可知router.options.routes[0]为根路由

   router.options.routes[0].children = [].concat(dynamicRoutes);

   // 这里为啥不把 * 匹配放到静态路由的最后面,是因为如果放置在静态路由最后面,作为一级路由,当url同前面的路由都不匹配时,会匹配到 *,这样一来,刷新页面时,由于还没加载动态路由,预期和动态路由匹配的url,会匹配到静态路由的 *,然后跳转404页面。

   if (router.options.routes[router.options.routes.length - 1].path != "*") {

     router.options.routes = router.options.routes.concat([

       {

         path: "*",

         name: "notFound",

         component: NotFound

       }

     ]);

   }

 

   // 添加路由,让路由生效

   router.addRoutes(router.options.routes);

 

   // 存储导航菜单list数据

   store.commit("setNavMenu", navMenuData);

 

   // 设置菜单为已加载状态

   store.commit("setMenuRouteLoadStatus", true);

   next();

   if (to.matched.length == 0) {

     router.push(to.path);

   }

 } else {

   // 本地sessionStorage获取不到,从服务器端读取

   api.menu

     .getNavMenuData()

     .then(res => {

       // 获取动态路由

       let dynamicRoutes = getDynamicRoutes(res.data);

 

       // 添加路由

       router.options.routes[0].children = [].concat(dynamicRoutes);

 

       // 如果要添加为一级路由,则按如下方式拼接路由

       // router.options.routes = staticRoute.concat(dynamicRoutes)

 

       // 注意,以下写法会导致添加的路由不起作用,为错误的写法

       // let otherVar=staticRoute.concat(dynamicRoutes)

       // router.addRoutes(otherVar); //添加的路由不起作用

 

       if (

         router.options.routes[router.options.routes.length - 1].path != "*"

       ) {

         router.options.routes = router.options.routes.concat([

           {

             path: "*",

             name: "notFound",

             component: NotFound

           }

         ]);

       }

 

       router.addRoutes(router.options.routes); //会产生重复路由,控制台会有warn提示,但是不影响,vue-router会自动去重,

 

       // 存储导航菜单list数据

       sessionStorage.setItem("navMenuData", JSON.stringify(res.data));

       store.commit("setNavMenu", res.data);

 

       // 设置菜单为已加载状态

       store.commit("setMenuRouteLoadStatus", true);

 

       next(); /* 注意:路由匹配是在router.addRoutes之前完成的,所以,即便使用router.addRoutes添加动态路由,也会出现to.matched.length也会等于0的情况,即没匹配到路由,所以to.matched.length等于0的情况下,再次执行router.push(to.path),这样,再次触发beforeEach函数调用)*/

       if (to.matched.length == 0) {

         router.push(to.path);

       }

     })

     .then(res => {

       // 保存用户权限标识集合

     })

     .catch(function(res) {

       console.log(res);

     });

 }

}

 

/**

* 获取动态(菜单)路由配置

* @param {*} menuList菜单列表

* @param {*} routes递归创建的动态(菜单)路由

*/

function getDynamicRoutes(menuList = [], parentRoute = []) {

 for (var i = 0; i < menuList.length; i++) {

   var route = {}; // 存放路由配置

   if (menuList[i].url && /\S/.test(menuList[i].url)) {

     // url不为空,且包含任何非空白字符

     route = {

       path: menuList[i].url,

       component: null,

       name: menuList[i].name,

       children: [],

       meta: {

         icon: menuList[i].icon,

         index: menuList[i].id,

         requireAuth: menuList[i].requireAuth // 可选值true、false 添加该字段,表示进入这个路由是需要登录的

       }

     };

 

     try {

       // 根据菜单URL动态加载vue组件,这里要求vue组件须按照url路径存储

       // 如url="sys/user",则组件路径应是"@/views/sys/user.vue",否则组件加载不到

       let array = [];

       if (menuList[i].url.startsWith("/")) {

         // 如果url 以 "/"打头,所以要先去掉左侧的 "/",否则组件路径会出现 @/views//sys/user的情况

         array = menuList[i].url.substring(1).split("/");

       } else {

         array = menuList[i].url.split("/");

       }

 

       let url = ""; // 存放url对应的组件路径

 

       // 组件所在目录及组件名称第一个字母大写,所以需要替换

       for (let i = 0; i < array.length; i++) {

         url +=

           array[i].substring(0, 1).toUpperCase() +

           array[i].substring(1) +

           "/";

       }

       url = url.substring(0, url.length - 1); // 去掉最右侧的 '/'

       route["component"] = resolve => require([`@/views/${url}`], resolve);

     } catch (e) {

       console.log("根据菜单URL动态加载vue组件失败:" + e);

     }

 

     if (menuList[i].children && menuList[i].children.length >= 1) {

       getDynamicRoutes(menuList[i].children, route["children"]);

     }

   } else {

     if (menuList[i].children && menuList[i].children.length >= 1) {

       getDynamicRoutes(menuList[i].children, parentRoute);

     }

   }

   if (JSON.stringify(route) != "{}") {

     parentRoute.push(route);

   }

 }

 return parentRoute;

}

 

export default router;

 

 

 

代码片段(src/Login.vue)

methods: {

   login() {

       let userInfo = {

           account:this.loginForm.account,

           password:this.loginForm.password,

           captcha:this.loginForm.captcha

       };

       this.$api.login.login(userInfo)

       .then(res=> {

           if (res.success) {

               Cookies.set("token", res.data.token); // 保存token到Cookie

               sessionStorage.setItem("userName", userInfo.account); // 保存用户信息到本地会话

               this.$store.commit("setMenuRouteLoadStatus", false); // 重置导航菜单加载状态为false

               if (JSON.stringify(this.$route.query) != "{}") { // 不需要跳转到登录前的页面

                   this.$router.push(this.$route.query.redirect); // 登录成功,跳转之前页面

                } else {

                   this.$router.push("/")

                }

           } else {

               this.$message({

                   message:res.msg,

                   type:"error"

               });

           }

           this.loading = false;

       })

       .catch(res=> {

           this.$message({

               message:res.message,

               type:"error"

           });

       });

   },

 

目录
相关文章
|
JavaScript
在 Vue 中处理组件选项与 Mixin 选项冲突的详细解决方案
【10月更文挑战第18天】通过以上的分析和探讨,相信你对在 Vue 中使用 Mixin 时遇到组件选项与 Mixin 选项冲突的解决方法有了更深入的理解。在实际开发中,要根据具体情况灵活选择合适的解决方案,以确保代码的质量和可维护性。
578 7
|
11月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件的实现代码:支持自定义表情库、快捷键发送和输入框联动的聊天表情解决方案
本文详细介绍了在 Vue 项目中实现一个功能完善、交互友好的表情包输入组件的方法,并提供了具体的应用实例。组件设计包含表情分类展示、响应式布局、与输入框的交互及样式定制等功能。通过核心技术实现,如将表情插入输入框光标位置和点击外部关闭选择器,确保用户体验流畅。同时探讨了性能优化策略,如懒加载和虚拟滚动,以及扩展性方案,如自定义主题和国际化支持。最终,展示了如何在聊天界面中集成该组件,为用户提供丰富的表情输入体验。
797 8
|
资源调度 JavaScript 前端开发
路由管理:Vue Router的使用和配置技巧
【10月更文挑战第21天】路由管理:Vue Router的使用和配置技巧
369 3
|
JavaScript API
vue 批量自动引入并注册组件或路由等等
【10月更文挑战第12天】 vue 批量自动引入并注册组件或路由等等
|
JavaScript 前端开发 API
vue3中常用插件的使用方法:按需引入自定义组件,自动导入依赖包,自动生成路由,自动生成模拟数据
vue3中常用插件的使用方法:按需引入自定义组件,自动导入依赖包,自动生成路由,自动生成模拟数据
2359 0
|
JavaScript 前端开发 UED
vue中vue-router路由懒加载(按需加载)的作用以及常见的实现方法
vue中vue-router路由懒加载(按需加载)的作用以及常见的实现方法
966 1
|
JavaScript UED
"Vue实战技巧大揭秘:一招解决路由跳转页面不回顶部难题,让你的单页面应用用户体验飙升!"
【10月更文挑战第23天】在Vue单页面应用中,点击路由跳转时,默认情况下页面不会自动滚动到顶部,这可能影响用户体验。本文通过一个新闻网站的案例,介绍了如何使用Vue-router的全局前置守卫和`scrollBehavior`方法,实现路由跳转时页面自动滚动到顶部的功能,提升用户浏览体验。
745 0
|
JavaScript 前端开发 索引
Vue3 + Vite项目实战:常见问题与解决方案全解析
Vue3 + Vite项目实战:常见问题与解决方案全解析
1584 0
|
JavaScript API
Vue中使用require.context()自动引入组件和自动生成路由的方法介绍
Vue中使用require.context()自动引入组件和自动生成路由的方法介绍
606 0
|
8月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
675 2

热门文章

最新文章