Bootstrap

【前端之Vue3路由机制router】

路由简介

路由 就是根据不同的 URL 地址展示不同的内容或页面

  • 单页应用程序(SPA)中,路由可以实现不同视图之间的无刷新切换,提升用户体验
  • 路由还可以实现页面的认证和权限控制,保护用户的隐私和安全
  • 路由还可以利用浏览器的前进与后退,帮助用户更好地访问历史页面

路由入门案例

【案例:启动程序,进入首页,点击首页和列表可以自由切换】

1、创建项目安装依赖(含路由依赖)

  • npm create vite //创建项目,
  • cd 项目文件夹 //进入项目文件夹
  • npm installnpm i //安装项目需求依赖
  • npm install vue-routernpm install vue-router@4 --save // 安装vue-router

2、创建页面和组件
在这里插入图片描述
router-view:该标签会被替换成具体的.vue对象, 一个视图上可以同时存在多个router-view,每个router-view可以设置成展示哪个组件

App.vue

<script setup></script>

<template>
  <div>
    <!-- 父组件跳转到不同页面 -->
    <h2>1、父组件 App.vue 跳转到不同页面按钮</h2>
    <router-link to="/home">Home页面 按钮</router-link><br />
    <router-link to="/list">List页面 按钮</router-link><br />
    <router-link to="/add">Add页面 按钮</router-link><br />
    <router-link to="/update">Update页面 按钮</router-link><br />
    <hr />
    此页面当前部分内容【不会随着下面路由内容的改变而改变】
    <hr />
    <!-- 该标签会被切换成具体的.vue文件
    一个视图上可以同时存在多个router-view,每个router-view可以设置成展示哪个组件
    一般来说,实际场景中一个.vue中用一个router-view标签就可以解决99%的业务需求,此处了解即可;
     -->
    <router-view></router-view>
    <hr />
    此页面当前部分内容【不会随着下面路由内容的改变而改变】
    <hr />
  </div>
</template>

<style scoped></style>

Home.vue

<script setup></script>

<template>
  <div>
    <h2>2、兄弟组件界面之间的跳转</h2>
    <router-link to="/list">Home页面跳转List页面</router-link><br />
    <h1>Home页面</h1>
  </div>
</template>

<style scoped></style>

Add.vue

<script setup></script>

<template>
  <div>
    <h2>2、兄弟组件界面之间的跳转</h2>
    <router-link to="/update">Add页面跳转Update页面</router-link><br />
    <h1>Add页面</h1>
  </div>
</template>

<style scoped></style>

List.vue

<script setup></script>

<template>
  <div>
    <h2>2、兄弟组件界面之间的跳转</h2>
    <router-link to="/add">List页面跳转Add页面</router-link><br />
    <h1>List页面</h1>
  </div>
</template>

<style scoped></style>

Update.vue

<script setup></script>

<template>
  <div>
    <h2>2、兄弟组件界面之间的跳转</h2>
    <router-link to="/home">Update页面跳转Home页面</router-link><br />
    <h1>Update页面</h1>
  </div>
</template>

<style scoped></style>

3、准备路由配置

  • 1.导入路由创建的相关方法
  • import { createRouter, createWebHashHistory } from "vue-router";
  • 2.创建路由对象,声明路由规则
  • 使用 createRouter(),该方法有两个属性:history 用于记录路由的历史; routes:用于定义多个不同路径和组件之间的对应关系
  • 3.对外暴露路由对象
  • export default router;;
  • 4.导入创建的add、list等.vue组件
    在这里插入图片描述

router.js

//导入路由创建的相关方法
import { createRouter, createWebHashHistory } from "vue-router";
/* 导入.vue组件 */
import Home from "../components/Home.vue";
import List from "../components/List.vue";
import Add from "../components/Add.vue";
import Update from "../components/Update.vue";
//创建路由对象,声明路由规则
const router = createRouter({
  //history 用于记录路由的历史
  history: createWebHashHistory(),
  //routes:用于定义多个不同路径和组件之间的对应关系
  routes: [
    {
      path: "/",
      component: Home,
    },
    {
      path: "/home",
      component: Home,
    },
    {
      path: "/list",
      component: List,
    },
    {
      path: "/add",
      component: Add,
    },
    {
      path: "/update",
      component: Update,
    },
  ],
});
//对外暴露路由对象
export default router;

4、main.js引入router配置

  • 在整个App.vue中可以使用路由:
    import router from "./routers/router.js";
    const app = createApp(App);
    app.use(router);
    app.mount("#app");

main.js

import { createApp } from "vue";

import App from "./App.vue";
//在整个App.vue中可以使用路由
import router from "./routers/router.js";

const app = createApp(App);
app.use(router);
app.mount("#app");

5、启动测试
在这里插入图片描述

路由router入门案例演示

路由重定向

直接在路由的创建方法中使用 redirect 即可;
在页面访问otherAll时,会重定向到list页面
在这里插入图片描述
在这里插入图片描述

编程式路由(useRouter)

  • 普通声明式路由:
    <router-link to="/list">list页</router-link>
    这种路由,to中的内容目前是固定的,点击后只能切换/list对象组件
  • 编程式路由(useRouter):
    useRouter方法返回的是一个 router 对象,通过useRouter,来实现动态路由(编程式路由)

注意 编程式路由 不是 动态路由 ,
动态路由

案例需求: 通过普通按钮配合事件绑定实现路由页面跳转,不直接使用router-link标签
上面案例代码不变,直接在App.vue引入useRouter即可,代码如下:

App.vue

<script setup>
/* 导入useRouter 方法,useRouter 方法返回的是一个 router 对象,你可以用它来做如导航到新页面、返回上一页面等操作 */
import { useRouter } from "vue-router";
/* 导入ref存储页面逻辑代码 */
import { ref } from "vue";
//创建动态路由对象
const router = useRouter();
let routePath = ref("");

function goMyPage() {
  //编程式路由实现页面跳转
  router.push(routePath.value);
}
</script>

<template>
  <div>
    <!-- 父组件跳转到不同页面 -->
    <h1 style="color:red">Ⅰ、声明式路由:1、父组件 App.vue 跳转到不同页面按钮</h1>
    <router-link to="/home">Home页面 按钮</router-link><br />
    <router-link to="/list">List页面 按钮</router-link><br />
    <router-link to="/add">Add页面 按钮</router-link><br />
    <router-link to="/update">Update页面 按钮</router-link><br />
    <hr />
    此页面当前部分内容【不会随着下面路由内容的改变而改变】
    <hr />
    <!-- 该标签会被切换成具体的.vue文件
    一个视图上可以同时存在多个router-view,每个router-view可以设置成展示哪个组件
    一般来说,实际场景中一个.vue中用一个router-view标签就可以解决99%的业务需求,此处了解即可;
     -->
    <router-view></router-view>
    <hr />
    此页面当前部分内容【不会随着下面路由内容的改变而改变】
    <hr />

    <!-- 2、编程式路由 -->
    <h1 style="color:red">Ⅱ、编程式路由</h1>
    <br />

    <!-- v-model="routePath" 响应式数据的双向绑定,来绑定一个路径 -->
    <button @click="goMyPage()">GO</button><input type="text" v-model="routePath" />
    <hr />
  </div>
</template>

<style scoped></style>

演示过程

编程式路由

路由传参

在这里插入图片描述
在这里插入图片描述

App.vue

<script setup>
/* 导入useRouter 方法,useRouter 方法返回的是一个 router 对象,你可以用它来做如导航到新页面、返回上一页面等操作 */
import { useRouter } from "vue-router";
/* 导入ref存储页面逻辑代码 */
import { ref } from "vue";
//创建动态路由对象
const router = useRouter();

//编程式路由传参方法
function showDetail(id, language) {
  router.push(`/showDetail/${id}/${language}`);
}

//编程式键值对传参方法
function showDetail2(id, language) {
  router.push(`/showDetail2?id=${id}&language=${language}`);
}

</script>

<template>
  <div>
    <router-link to="/showDetail/1/北京">声明式路由路径传参</router-link>
    <hr />
    <button @click="showDetail(2, '春天')">编程式路由传参</button>
    <br />
    <br />
    <hr /> 
    <hr /> 
    <hr /> 
    <br /> 
    <router-link to="/showDetail2?id=3&language=夏天 ">声明式路由键值对传参</router-link>
    <hr />
    <button @click="showDetail2(4, '伊春')">编程式键值对传参</button>
    <hr />


    <router-view></router-view>
  </div>
</template>

<style scoped></style>

ShowDetail.vue

<script setup>
/* 注意:下面引入的userRoute方法是用来接收传递过来的路径参数的,
而之前  import {useRouter} from 'vue-router' 中的userRouter是用来做编程式路由的 */
import { useRoute } from "vue-router";
//响应式数据
//导入生命周期的方法
import { ref, onUpdated } from "vue";
let languageId = ref(0);
let languageName = ref("");

/*  获取当前的route对象
route.param:表示路径参数
route.query:表示键值对参数

*/
let route = useRoute();
languageId.value = route.params.id;
languageName.value = route.params.language;

/* 如果同一个界面【如App.vue中设置两种传参方式:1、声明式路由路径传参2、编程式路由方式】
设置两种传参方式,会出现下述问题:点击其中一个方式传参接收成功后,第二个传参接收失败的问题,
此时要借用Vue生命周期中的onUpdated方法来进行处理,当接收参数成功后,再重新赋值即可。
使用方法,导入生命周期的方法,重新赋值🆗!
*/

//onUpdated方法中重新赋值
onUpdated(() => {
  languageId.value = route.params.id;
  languageName.value = route.params.language;
});
</script>

<template>
  <div>
    <h1>ShowDetail1 路径传参</h1>
    <h2>{{ languageId }}{{ languageName }}非常棒!</h2>
  </div>
</template>

<style scoped></style>

ShowDetail2.vue

<script setup>
/* 注意:下面引入的userRoute方法是用来接收传递过来的路径参数的,
而之前  import {useRouter} from 'vue-router' 中的userRouter是用来做编程式路由的 */
import { useRoute } from "vue-router";
//响应式数据
//导入生命周期的方法
import { ref, onUpdated } from "vue";
let languageId = ref(0);
let languageName = ref("");

/*  获取当前的route对象
route.param:表示路径参数
route.query:表示键值对参数

*/
let route = useRoute();
// languageId.value = route.params.id;
// languageName.value = route.params.language;

/* 如果同一个界面【如App.vue中设置两种传参方式:1、声明式路由路径传参2、编程式路由方式】
设置两种传参方式,会出现下述问题:点击其中一个方式传参接收成功后,第二个传参接收失败的问题,
此时要借用Vue生命周期中的onUpdated方法来进行处理,当接收参数成功后,再重新赋值即可。
使用方法,导入生命周期的方法,重新赋值🆗!
*/

//onUpdated方法中重新赋值
// onUpdated(() => {
//   languageId.value = route.params.id;
//   languageName.value = route.params.language;
// });

//键值对传参
languageId.value = route.query.id;
languageName.value = route.query.language;
//onUpdated方法中重新赋值
onUpdated(() => {
  languageId.value = route.query.id;
  languageName.value = route.query.language;
});


</script>

<template>
  <div>
    <h1>ShowDetail2 键值对传参</h1>
    <h2>{{ languageId }}{{ languageName }}非常美丽!</h2>
  </div>
</template>

<style scoped></style>

main.js

import { createApp } from "vue";

import App from "./App.vue";
//在整个App.vue中可以使用路由
import router from "./routers/router.js";

const app = createApp(App);
app.use(router);
app.mount("#app");

router.js

//导入路由创建的相关方法
import { createRouter, createWebHashHistory } from "vue-router";
/* 导入.vue组件 */
import ShowDetail from "../components/ShowDetail.vue";
import ShowDetail2 from "../components/ShowDetail2.vue";
//创建路由对象,声明路由规则
const router = createRouter({
  //history 用于记录路由的历史
  history: createWebHashHistory(),
  //routes:用于定义多个不同路径和组件之间的对应关系
  routes: [
    { path: "/showDetail2", component: ShowDetail2 },
    /* "/showDetail/:id/:language"中的:id/:language就不是地址了,代表App.vue中
      <router-link to="/showDetail/1/java">声明式路由路径传参</router-link>
      路径中携带的参数  */
    { path: "/showDetail/:id/:language", component: ShowDetail },
  ],
});
//对外暴露路由对象
export default router;

演示过程

路由传参

路由守卫

在 Vue 3 中,路由守卫是用于在路由切换期间进行一些特定任务的回调函数。路由守卫可以用于许多任务,例如验证用户是否已登录、在路由切换前提供确认提示、请求数据等。Vue 3 为路由守卫提供了全面的支持,并提供了以下几种类型的路由守卫:

  • 1. 全局前置守卫:在路由切换前被调用,可以用于验证用户是否已登录、中断导航、请求数据等。
  • 2. 全局后置守卫:在路由切换之后被调用,可以用于处理数据、操作 DOM 、记录日志等。
  • 3. 守卫代码的位置: 在router.js
//全局前置路由守卫
router.beforeEach( (to,from,next) => {
    //to 下个页面到哪里去,是目标地包装对象  .path属性可以获取地址
    //from 是来源地包装对象,上个页面从哪里来 .path属性可以获取地址
    //next是方法,不调用默认拦截! next() 放行,直接到达目标组件
    //next('/地址')可以转发到其他地址,到达目标组件前会再次经过前置路由守卫
    console.log(to.path,from.path,next)

    //需要判断,注意避免无限重定向
    if(to.path == '/index'){
        next()
    }else{
        next('/index')
    }
    
} )

//全局后置路由守卫
router.afterEach((to, from) => {
    console.log(`Navigate from ${from.path} to ${to.path}`);
});

路由案例练习

登录案例登录以后才可以进入home,否则必须进入login
规定:如果是root用户并且密码是123456则登录成功

创建项目略,代码如下
在这里插入图片描述

App.vue

<script setup></script>

<template>
  <div>
    <router-view></router-view>
  </div>
</template>

<style scoped></style>

main.js

import { createApp } from "vue";

import App from "./App.vue";

//导入router路由对象
import router from "./routers/router";
//给app对象使用router对象,然后挂载到app上
const app = createApp(App);
app.use(router);
app.mount("#app");

Home.vue

<script setup>
let username = window.sessionStorage.getItem("username");
//导入响应式路由对象
import { useRouter } from "vue-router";
//创建对象
const router = useRouter();
function logout() {
  //1、先清除缓存
  window.sessionStorage.removeItem("username");
  //2、使用路由重定向到登录界面
  router.push("/login");
}
</script>

<template>
  <div>
    <h1>Home页面</h1>
    <h2>欢迎 {{ username }} 登录</h2>
    <button @click="logout()">退出登录</button>
  </div>
</template>

<style scoped></style>

Login.vue

<script setup>
//导入处理响应式数据的ref函数
import { ref } from "vue";

//路由跳转,usrRouter用来做编程式路由
import { useRouter } from "vue-router";

let router = useRouter();

/*
双向绑定
双向绑定: 响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变会同步更新到响应式数据

- 用户通过表单标签才能够输入数据,所以双向绑定都是应用到表单标签上的,其他标签不行
- v-model专门用于双向绑定表单标签的value属性,语法为 v-model:value='',可以简写为 v-model=''
- v-model还可以用于各种不同类型的输入,<textarea>、<select> 元素。
*/
let username = ref("");
let password = ref("");

//登录方法
function login() {
  //获取用户名和密码,校验,如果是root用户并且密码是123456则登录成功
  //登录成功后跳转到/home页面
  //登录失败,不跳转,直接给出提示
  if (username.value == "root" && password.value == "123456") {
    //将用户名保存在浏览器上,使用sessionStorage 或 localStorage
    //可以参考BOM编程对象结构:window 顶级对象代表整个浏览器窗口
    //- localStorage对象        window对象的属性之一,代表浏览器的本地数据持久化存储,在浏览器中存储 key/value 对。没有过期时间。
    //- sessionStorage对象      window对象的属性之一,代表浏览器的本地数据会话级存储,在浏览器中存储 key/value 对。 在关闭窗口或标签页之后将会删除这些数据。
    window.sessionStorage.setItem("username", username.value);
    //路由跳转
    router.push("/home");
  } else {
    alert("用户名或密码错误/(ㄒoㄒ)/~~,请尝试重新登录(*^_^*)!");
  }
}
</script>

<template>
  <div>
    <h1>Longin页面</h1>
    账号:<input type="text" v-model="username" /><br />
    密码:<input type="password" v-model="password" /><br />
    <button @click="login()">登录</button>
  </div>
</template>

<style scoped></style>

router.js

//导入创建路由对象的方法
import { createRouter, createWebHashHistory, useRoute } from "vue-router";

//引入组件
import Home from "../components/Home.vue";
import Login from "../components/Login.vue";
//创建路由对象
const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: "/",
      component: Home,
    },
    {
      path: "/home",
      component: Home,
    },
    {
      path: "/login",
      component: Login,
    },
  ],
});

//为了防止用户直接登录home页面,可以使用【路由的前置守卫】进行校验登录
router.beforeEach((to, from, next) => {
  //获取上一页的路径进行如下判断
  if (to.path == "/login") {
    //如果是要去登录页面,直接放行
    next();
  } else {
    //其他资源必须登录之后,才会放行,如果没登陆,则重定向到登录试图
    //从sessionStorage中获取用户名,判断先前是否登录,如果登录过放行,否则重定向到登录界面
    const username = sessionStorage.getItem("username");
    if (username != null) {
      //不为null,说明登录过,直接放行
      next();
    } else {
      //否则重定向到登录界面
      next("/login");
    }
  }
});
//对外暴露路由对象
export default router;

结果演示

路由守卫练习

;