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

 

by:授客 QQ1033553122

 

开发环境

 

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

Vue 实现动态路由及登录&404页面跳转控制&页面刷新空白解决方案的更多相关文章

  1. vue的动态路由(登录之后拿到动态路由通过addRouters()动态添加路由)

    登录后我们拿到路由动态路由,后端传的数据可能为这个 { path: '/index', meta: { title: '首页', icon: 'icon-shouye', tab_index: , / ...

  2. 使用VUE开发用户后台时的动态路由问题、按钮权限问题以及其他页面处理问题

    如今前后端分离是大势所趋,笔者虽然是做后台的,但也不得不学学前端的流行框架VUE -_-||| . 为了学习VUE,笔者搭建了一个简单的用户后台,以此来了解VUE的开发思路(注:本项目不用于实际开发, ...

  3. 直击--vue项目微信小程序页面跳转web-view不刷新-根源

    背景 最近项目需要适配小程序,项目是使用了vue开发的网站,其中改造方式是,每个页面都使用小程序创建一个页面通过web-view来显示指定页面的. 在没有使用小程序时,路由跳转时,刷新页面等等,这个是 ...

  4. js关闭当前页面跳转新页面

    页面代码: <p class="info"><span style="font-weight: bold">所属项目:</span ...

  5. 从上一个页面跳入新页面时,如何拿URL中的参数

    var url = document.URL; //获取当前页面的url var urlA = url.split('?');//以url中的问号进行分割; var goodscode = urlA[ ...

  6. vue --》动态路由的实现 (根据用户的权限来访问对应的模块)

    为了设置用户的访问权限,所以在一个vue工程中,往往需要前端和后端开发人员共同配合设置动态路由, 进而实现根据用户的权限来访问对应的模块 1.首先在登录页面,前端跟据后端返回的路由信息进行配置,配置后 ...

  7. 浅谈vue之动态路由匹配

    在日常开发过程中,可能会遇到一些类似于新闻详情页的内容,需要把所有详情页映射到同一组件上,这是动态路由匹配的应用场景之一.在使用的过程中,也遇到过一些小坑,此篇做个简要的总结说明: 基本使用 { pa ...

  8. 十六、React 渲染数据注意事项、以及react-router4.x中使用js跳转路由(登录成功自动跳转首页)

    一.React加载数据流程回顾 先看上一节的产品详情代码:https://blog.csdn.net/u010132177/article/details/103184176 [Pcontent.js ...

  9. TP3.2.3 页面跳转后 Cookie 失效 —— 参考解决方案

    一.问题描述 接手一个项目,使用ThinkPhp3.2.3,在线上环境( Centos7.4 + Nginx1.14 + MySQL5.7 + PHP7.2.4 )运行没有问题, 在本地环境( php ...

随机推荐

  1. 理解Javascript的柯里化

    前言 本文1454字,阅读大约需要4分钟. 总括: 本文以初学者的角度来阐述Javascript中柯里化的概念以及如何在工作中进行使用. 原文地址:理解Javascript的柯里化 知乎专栏: 前端进 ...

  2. markdown常用语法使用笔记+使用技巧(持续更新......)

    参考引用内容: 简书教程 一 基本语法 1. 标题 语法: 在想要设置为标题的文字前面加#来表示,一个#是一级标题,二个#是二级标题,以此类推.支持六级标题. 注:标准语法一般在#后跟个空格再写文字 ...

  3. spring cloud oauth2搭建认证中心与资源中心

    一 认证中心搭建 添加依赖,如果使用spring cloud的话,不管哪个服务都只需要这一个封装好的依赖即可 <dependency> <groupId>org.springf ...

  4. 单点登陆(SSO)

    一.背景 在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便.但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要 ...

  5. 微信小程序 客户端时间 与 服务端时间

    服务端时间 db.serverDate(); 在操作数据库,上传数据的时候可以使用服务端时间 wx.cloud.init();//初始化云 const db = wx.cloud.database() ...

  6. 1240: 函数strcmp的设计

    #include <string.h>#include <stdio.h>int mycmp(char*s1,char*s2);int main(){ int sum; cha ...

  7. Java并发编程(二):volatile关键字

    volatile是Java虚拟机提供的轻量级的同步机制.volatile关键字有如下两个作用,一句话概括就是内存可见性和禁止重排序. 1)保证被volatile修饰的共享变量对所有线程总是可见的,也就 ...

  8. centos6安装lamp

    1.安装Apache [root@localhost ~]# yum -y install httpd 设置开启自启动 [root@localhost ~]# chkconfig httpd on 启 ...

  9. 今天更新IDEA后,我依旧要永久激活(支持2019.3.3版本)

    起因 今天一早用IDEA写代码,看到有下角有提示更新,有点强迫症的我,就手欠的又点了下更新,结果尼玛悲剧了,居然许可证过期,IDEA过期了,如下图所示: 就想用下新功能,就这样对我,就给两天的使用时间 ...

  10. ASP.NET Core on K8S 入门学习系列文章目录

    一.关于这个系列 自从2018年底离开工作了3年的M公司加入X公司之后,开始了ASP.NET Core的实践,包括微服务架构与容器化等等.我们的实践是渐进的,当我们的微服务数量到了一定值时,发现运维工 ...