动态路由和路由导航守卫及其案例分析
为什么需要动态路由?
动态路由其实用的不多,在实际开发中,如果遇到权限分配问题,比如对于一个公司人员的后台管理系统,那对不同成员的权限肯定不同,对于人事部,他们有权限进入成员表对人员的流动进行管理,对于技术部,他们有权上传任务进度来进行团队协作等等。对于不同人员,界面的渲染也不能相同。在有一些公司中可能会采用隐藏组件来实现权限的分配,但这样治标不治本:路由还是注册了,理论上只要知道路径,即使没有相应的组件来进入目标页面,也可以通过映射关系进入。这就会产生bug,所以有时会采用动态路由。
动态路由,顾名思义,就是在一定条件下才会注册路由,要不然根本不注册,脱离了路由就失去了映射关系,这样就不会出现bug了。
动态路由
其实动态路由核心就只有一个:addRoute方法,在这个方法中,可以传入一个对象,填入path,component,query等参数。
let isAdmin = true
if (isAdmin) {
router.addRoute({
path: "/admin",
component: () => import("@/Views/Admin.vue")
})
}
//嵌套写法
if (isAdmin){
router.addRoute("home",{
path:"/home/vip",
component: ()=> import("")
})
}
以上只是模拟一下权限的分配,真实情况下权限的分配是根据服务器传回来的响应来决定的。
当isAdmin为true时,说明权限为管理员,这时我们就给其注册路由,创建Admin.vue与"/admin"的映射关系。
当然,想要移除也是同理,使用removeRoute方法,传入路由的name。
路由导航守卫⭐
了解路由导航守卫前,我们只要了解一个业务就能很好理解:
对于刚进入淘宝的游客,他可以浏览商品但是当购买商品时不会跳转到购买界面而是会重定向到登录/注册界面,只有当注册/登录完成后服务器返回用户特定的token后才会允许他进入购买界面,生成token那是后端的逻辑,暂且不论。对于重定向,路由导航守卫起到很大作用:导航守卫会在用户每次前往某个界面前先检查一下用户是否满足条件,如果不满足条件就会执行回调函数,一般是重定向,在满足条件后可以正常进入。
视频分析
接下来,我们结合一个视频案例来说明路由导航守卫最终的效果
2024-09-03 22-34-47
代码分析
在这篇文章仅仅展示路由导航守卫用到的代码,关于其他的代码可以翻阅上篇文章。
App.vue
<template>
<div class="app">
<div class="main-nav">
<!-- <router-link to="/home" replace>首页</router-link> replace 替换路径,不会记录历史路径-->
<!-- <router-link to="/home" active-class="link-class">首页</router-link> active-class指定选中的元素附带的className -->
<ul class="nav-bar">
<li @click="homeLiClick">首页</li>
<li @click="aboutLiClick">关于</li>
<li @click="userLiClick">用户</li>
</ul>
</div>
<router-view></router-view>
</div>
<!-- 编程式跳转页面 -->
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter()
function homeLiClick(){
router.push({
path:"/home",
query:{}
})
}
function aboutLiClick(){
router.push({
path:"/about",
query:{
name:"Lisman",
age:"18",
sex:"male"
}
})
}
function userLiClick(){
router.push({
path:"/user",
query:{
name:"Lisman",
password:"123456"
}
})
}
</script>
<style lang="less" scoped>
.main-nav {
.nav-bar{
list-style-type: none;
display: flex;
margin: 0;
padding: 0;
text-align: center;
height: 44px;
width: 100%;
line-height: 44px;
li{
flex: 1;
background-color: #454545;
color: whitesmoke;
font-size: 14px;
font-weight: bold;
cursor: pointer;
}
}
}
</style>
Login.vue
<template>
<div class="login">
<div class="container">
<div class="header">
<span>Admin Login</span>
</div>
<div class="content">
<div class="login-content">
<label for="username">
username
<input type="text" name="username">
</label>
<label for="password">
password
<input type="text" name="password">
</label>
<button @click="loginBtnClick">登录</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter()
function loginBtnClick(){
window.localStorage.setItem("token","12234FFASSD")
router.push({
path:"/user",
query:{
name:"Lisman",
password:"123456"
}
})
}
</script>
<style lang="less" scoped>
.login{
height: 1000px;
background-color: #c9c9c9;
position: relative;
.container{
height: 400px;
width: 50%;
background-color: #fff;
position: absolute;
top:200px;
left:25%;
border-radius: 10px;
.header{
width: 80%;
margin: 0 auto;
height: 100px;
text-align: center;
line-height: 100px;
span{
font-weight: bold;
font-size: 30px;
padding-bottom: 3px;
border-bottom: 2px solid black;
}
}
.content{
height: 255px;
background-color: aquamarine;
width: 75%;
margin: 0 auto;
border-radius: 10px;
.login-content{
width: 75%;
height: 100px;
margin: 0 auto;
padding-top: 30px;
position: relative;
label{
display: block;
margin-bottom: 20px;
margin-top: 30px;
margin-left: 100px;
}
button{
position: absolute;
top: 170px;
left: 340px;
}
}
}
}
}
</style>
User.vue
<template>
<div class="user">
<div class="container">
<div class="header">
<span>User Info</span>
</div>
<div class="content">
<div class="user-info">
<h2 class="username">Username : {{ $route.query.name }}</h2>
<h2 class="password">Password : {{ $route.query.password }}</h2>
<button @click="logoutBtnClick">退出登录</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter()
function logoutBtnClick(){
window.localStorage.removeItem("token")
router.push("/home")
}
</script>
<style lang="less" scoped>
.user{
height: 1000px;
background-color: #c9c9c9;
position: relative;
.container{
height: 400px;
width: 50%;
background-color: #fff;
position: absolute;
top:200px;
left:25%;
border-radius: 10px;
.header{
width: 80%;
margin: 0 auto;
height: 100px;
text-align: center;
line-height: 100px;
span{
font-weight: bold;
font-size: 30px;
padding-bottom: 3px;
border-bottom: 2px solid black;
}
}
.content{
height: 255px;
background-color: aquamarine;
width: 75%;
margin: 0 auto;
border-radius: 10px;
.user-info{
width: 75%;
height: 100px;
margin: 0 auto;
padding-top: 30px;
position: relative;
text-align: center;
h2{
padding-top: 20px;
}
}
}
}
}
</style>
index.js
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
//导入组件
// import Home from '@/Views/Home.vue'
// import About from '@/Views/About.vue'
//以下为路由懒加载,import导入可以做到分包处理,webpackChunkName可以给包指定名字
// const Home = import(/* webpackChunkName: 'Home' */"../Views/Home.vue")
// const About = import(/* webpackChunkName: 'About' */"../Views/About.vue")
//创建路由:映射关系
const router = createRouter({
//选择模式(Hash)
// history: createWebHashHistory(),
history: createWebHashHistory(),
routes: [
{
path: "/home",
component: () => import("../Views/Home.vue"),
//嵌套路由
children: [
{
path: "recommend",//相当于/home/recommend
component: () => import("../Views/Recommend.vue")
},
{
path: "ranking",
component: () => import("../Views/Ranking.vue")
},
{
path: "",
redirect: "/home/recommend"
}
]
},
{ path: "/about", component: () => import("../Views/About.vue") },
{ path: "/", redirect: "/home" },
{ path: "/:pathMatch(.*)", component: () => import("../Views/NotFound.vue") },
{ path: "/login", component: () => import("@/Views/Login.vue") },
{ path: "/user", component: () => import("@/Views/User.vue") }
]
})
// let isAdmin = true
// if (isAdmin) {
// router.addRoute({
// path: "/admin",
// component: () => import("@/Views/Admin.vue")
// })
// }
//嵌套写法
// if (isAdmin){
// router.addRoute("home",{
// path:"/home/vip",
// component: ()=> import("")
// })
// }
//路由守卫,每次跳转都会执行回调
router.beforeEach((to, from) => {
const token = window.localStorage.getItem("token")
if (to.path === "/user" && !token) {
return "/login"
}
})
//导出路由
export default router
框图分析
以下为图例:
其实有关路由导航守卫的只要一段代码,其他部分主要是界面和token的获取和设置。
router.beforeEach((to, from) => {
const token = window.localStorage.getItem("token")
if (to.path === "/user" && !token) {
return "/login"
}
})
beforeEach()方法会在进入每个路径前激活它的回调函数,它拥有两个参数:to,from,to为目标路由,from为原来的路由,我们首先检查一下用户是否拥有token,判断目标路径是否为/user,如果没有token,那么就满足条件而重定向到/login。如果拥有token,那么就不满足条件,返回undefined,在beforeEach中如果返回undefined那就不做操作,该去哪去哪,守卫不会作用。
当然守卫不只beforeEach一种,不同的守卫应结合实际开发情况来使用,关于其他的守卫文档我放在下面了:
https://router.vuejs.org/zh/guide/advanced/navigation-guards.html