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. flutter 与 android 混合开发

    现有的混合开发方式,都是存flutter项目在android系统或者iOS上面跑. 但是,实际情况是,我们需要在一个成熟的native项目上面,跑几个flutter页面,逐步的进行flutter的融合 ...

  2. 曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  3. 编译调试 .NET Core 5.0 Preview 并分析 Span 的实现原理

    很久没有写过 .NET Core 相关的文章了,目前关店在家休息所以有些时间写一篇新的

  4. https搭建(自签名证书)

    博客搬家: https搭建(自签名证书) 上一篇博客探究了https(ssl)的原理,为了贯彻理论落实于实践的宗旨,本文将记录我搭建https的实操流程,使用Apache2+ubuntu+openss ...

  5. Codeforces_851

    A.分三种情况. #include<bits/stdc++.h> using namespace std; int n,k,t; int main() { ios::sync_with_s ...

  6. Codeforces_835

    A.比较两人总时间. #include<bits/stdc++.h> using namespace std; int s,v1,v2,t1,t2; int main() { ios::s ...

  7. 概念验证:在Kubernetes中部署ABAP

    对于将SAP ABAP应用服务器组件容器化和在Kubernetes中部署它们,我们在SPA LinuxLab中做了概念验证(PoC),本文将介绍一些我们的发现和经验.本文会也会指出这项工作的一些潜在的 ...

  8. Qt中字符串和数值之间的转换

    来自<Qt5.9 C++开发指南> 普通数值和字符串之间的转换 一.从字符串转换为数值 QString类从字符串转换为整数的函数有: int QString::toInt(bool *ok ...

  9. 文件共享服务之Samba

    一.Samba服务概述 在Windows网络环境中.机之间进行文件打印和共享是通过微软自己的SMB/CIFS协议来实现的.SMB(服务消息块)和CIFS(通用互联网文件系统)这两个都是微软的私有协议 ...

  10. 来看看你对Python变量理解到位了没有

    变量是编程的基础概念,Python 的变量也看似很简单,但是如果理解不当,生搬硬套,可能会遇到一些麻烦. 下面用 10 个代码示例展示 Python 的 变量 本质. 以下内容有对应的 视频 手把手详 ...