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

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

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

by:授客 QQ:1033553122

开发环境

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"

});

});

},

菜单数据

data: [{

id: 1,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 0,

parentName: null,

name: "首页",

url: "/home",

perms: null,

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 1,

level: 0,

children: [{

id: 2,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 1,

parentName: null,

name: "首页二级菜单1",

url: "",

perms: null,

requireAuth: true,

type: 1,

icon: "fa fa-home fa-lg",

orderNum: 1,

level: 0,

children: [{

id: 3,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 2,

parentName: null,

name: "首页三级菜单1",

url: "",

perms: null,

requireAuth: true,

type: 1,

icon: "fa fa-home fa-lg",

orderNum: 1,

level: 0,

children: [{

id: 4,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 3,

parentName: null,

name: "首页四级菜单1",

url: "/home/level4Menu1",

perms: null,

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 1,

level: 0,

children: []

}]

},

{

id: 5,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 2,

parentName: null,

name: "首页三级菜单2",

url: "/home/level3Menu2",

perms: null,

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 2,

level: 0,

children: []

}

]

},

{

id: 6,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 1,

parentName: null,

name: "首页二级菜单2",

url: "",

perms: null,

requireAuth: true,

type: 1,

icon: "fa fa-home fa-lg",

orderNum: 2,

level: 0,

children: [{

id: 7,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 6,

parentName: null,

name: "首页三级菜单3",

url: "/home/level3Menu3",

perms: null,

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 1,

level: 0,

children: []

}]

}

]

},

{

id: 8,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 0,

parentName: null,

name: "工作台",

url: "/workbench",

perms: null,

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 2,

level: 0,

children: []

},

{

id: 9,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 0,

parentName: null,

name: "测试视图",

url: "/testerView",

perms: null,

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 3,

level: 0,

children: [{

id: 10,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 9,

parentName: null,

name: "测试视图二级菜单1",

url: "",

perms: null,

requireAuth: true,

type: 1,

icon: "fa fa-home fa-lg",

orderNum: 1,

level: 0,

children: [{

id: 11,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 10,

parentName: null,

name: "测试视图三级菜单1",

url: "/testerView/level3Menu1",

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 1,

level: 0,

children: []

},

{

id: 12,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 10,

parentName: null,

name: "测试视图三级菜单2",

url: "/testerView/level3Menu2",

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 2,

level: 0,

children: []

}

]

}]

},

{

id: 13,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 0,

parentName: null,

name: "配置",

url: "/settings",

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 4,

level: 0,

children: []

},

{

id: 14,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 0,

parentName: null,

name: "其它",

url: "",

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 5,

level: 0,

children: [{

id: 15,

createBy: null,

createTime: null,

lastUpdateBy: null,

lastUpdateTime: null,

parentId: 14,

parentName: null,

name: "其它菜单",

url: "/other",

requireAuth: true,

type: 0,

icon: "fa fa-home fa-lg",

orderNum: 1,

level: 0,

children: []

}]

}

]

参考链接

https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue 结合html2canvas和jsPDF实现html页面转pdf

    E:\MyProjects\TMP\frontend>npm install html2canvas

    授客
  • JavaScript 基于HTML5 canvas 获取文本占用的像素宽度

    授客
  • loadrunner 场景设计-添加Unix、Linux Resources计数器

    Linux CentOS为例(Linux、Unix虽说大同小异,但是具体落实到配置等实际执行还是有差别的,以下仅供参考

    授客
  • dart 如何优雅的避空

    对于每一个程序员来说,空指针异常应该是基本都会遇到过的异常,而且这个异常出现的概率还比较大。

    AndroidTraveler
  • 如何使用curl命令调用CM的API动态配置Yarn资源池

    在使用CDH集群大数据平台过程中,用户会有需求在自己的统一管理平台上通过API接口能够动态的设置Yarn资源池,Cloudera Manager提供了丰富的AP...

    Fayson
  • SQL,想说爱你并不是太容易的事

    不少人留言和留消息,只有一位差不多触及到了问题在哪里。公布一下答案之前先把题目贴一下。 ? ? ? 这道题目里面的坑主要在null这个东西。我们都知道SQL是基...

    用户1564362
  • 99%的高级程序员都这样使用null

    如果使用某个对象或对象里属性前先判断是否为null,那就需要思考一下你的代码是否已经烂掉了。 null是什么意思,你能说清楚它的意图吗?方法返回了null,是出...

    JavaQ
  • Java中有关Null的9问题

    Java中有关Null的9问题 对于Java程序员来说,null是令人头痛的东西。时常会受到空指针异常(NPE)的骚扰。连Java的发明者都承认...

    用户1289394
  • JTable常见用法细则+设置某列可编辑+滚动表格

    JTable常见用法细则 JTable是Swing编程中很常用的控件,这里总结了一些常用方法以备查阅.欢迎补充,转载请注明作者与出处. 一. 创建表...

    YGingko
  • Java函数式开发——优雅的Optional空指针处理

    空闲时会抽空学习同在jvm上运行的Groovy和Scala,发现他们对null的处理比早期版本Java慎重很多。在Java8中,Optional为函数式编程的n...

    哲洛不闹

扫码关注云+社区

领取腾讯云代金券