一、路由
路由的本质就是一种对应关系,根据不同的URL请求,返回对应不同的资源。那么url地址和真实的资源之间就有一种对应的关系,就是路由。路由分为:后端路由和前端路由。
1.1 路由分为两大类:
前端路由:Hash地址与组件之间的对应关系。
- SPA与前端路由之间的关系:SPA指的是一个web网站只有唯一的一个HTML页面,所有组件的展示与切换都在这唯一一个页面内完成。此时不同组件之间的切换,要依赖于前端路由来完成。
后端路由:请求方式、请求地址与function处理函数之间的对应关系。
前端路由的工作方式:
- 1、用户点击了页面上的路由链接
- 2、导致了URL地址栏中的Hash值发生了变化
- 3、前端路由监听到了Hash地址的变化
- 4、前端路由把当前Hash地址对应的组件渲染到浏览器中。
二、vue-router 的基本使用
vue-route是vue.js官方给出路由解决方案,只能结合vue项目使用,能够轻松管理SPA项目中的组件切换。
注意版本:vue-router 3.x只能结合vue2使用,vue-router 4.x只能结合vue3使用!
- 1、安装 vue-router
npm i vue-router@next -S
- 2、定义路由组件:在项目中定义所需的组件,将来要用vue-router控制它们的展示与切换。
- 3、声明路由链接和占位符:使用
<router-link>
标签声明路由链接,使用<router-view>
标签声明路由占位符。
<!-- App.vue -->
<template>
<div>
vue-router 的基本使用
<!-- 声明路由链接 -->
<router-link to="/home">首页</router-link>
<router-link to="/movie">电影</router-link>
<router-link to="/about">关于</router-link>
<hr />
<!-- 路由的占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'MyApp',
}
</script>
- 4、创建路由模块:在项目中创建router.js路由模块,在其中按如下步骤创建并得到路由对象:
- 从vue-router中按需导入两个方法
- 导入需要使用路由控制的组件
- 创建路由实例对象
- 向外共享路由实例对象
- 在main.js中导入并挂载路由模块
// router.js
// 1、从vue-router按需导入两个方法
// createRouter方法用于创建路由实例对象
// createWebHashHistory方法用于指定路由的工作模式(hash模式)
import { createRouter, createWebHashHistory } from 'vue-router'
// 2、导入需要使用路由控制的组件
import Home from './MyHome.vue'
import Movie from './MyMovie.vue'
import About from './MyAbout.vue'
// 3、创建路由对象
const router = createRouter({
// 通过history属性,指定路由的工作模式
history: createWebHashHistory(),
// 自定义路由高亮的 class 类名,会覆盖掉默认的router-link-active类名
linkActiveClass: 'active-router',
// 通过routes数组,声明路由的匹配规则。
// path是hash地址,component是要展示的组件,redirct是要重定向到的新地址
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About },
],
})
// 4、导出路由对象
export default router
// main.js
import { createApp } from 'vue'
import App from './components/start/App.vue'
// 5、在main.js中导入并挂载路由模块
import router from './components/start/router'
import './assets/css/bootstrap.css'
import './index.css'
const app = createApp(App)
// 挂载路由模块
app.use(router)
app.mount('#app')
-
5、路由重定向 路由重定向:用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面。
-
通过路由规则的redirect属性,指定一个新的路由地址。
-
6、路由高亮 将激活的路由链接进行高亮显示:
- ① 使用默认的高亮class类:被激活的路由链接,默认会应用一个叫做router-link-active的类名,开发者可以使用此类名选择器,在index.css文件中为激活的路由链接设置高亮样式。
- ② 自定义路由高亮的class类:在创建路由的实例对象时,开发者可以基于linkActiveClass属性,自定义路由链接被激活时所应用的类名。
/* index.css */
.router-link-active {
background-color: red;
color: white;
font-weight: bold;
}
.active-router {
background-color: blue;
color: white;
font-weight: bold;
}
三、vue-router 的高级用法
2.1 嵌套路由
嵌套路由:通过路由实现组件的嵌套展示。
- 声明子路由链接和子路由占位符
- 在父路由规则中,通过children属性嵌套声明子路由规则
<!-- MyAbout.vue -->
<template>
<div>
<h3>MyAbout 组件</h3>
<!-- 声明子路由链接 -->
<router-link to="/about/tab1">Tab1</router-link>
<router-link to="/about/tab2">Tab2</router-link>
<hr>
<!-- 声明子路由占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'MyAbout',
}
</script>
// router.js
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from './MyHome.vue'
import Movie from './MyMovie.vue'
import About from './MyAbout.vue'
import Tab1 from './tabs/MyTab1.vue'
import Tab2 from './tabs/MyTab2.vue'
const router = createRouter({
history: createWebHashHistory(),
linkActiveClass: 'active-router',
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{
path: '/about',
component: About,
// 嵌套路由的重定向
redirect: '/about/tab1',
// 通过 children 属性嵌套声明子级路由规则
// 注意:子路由的path不要以/开头
children: [
{ path: 'tab1', component: Tab1 },
{ path: 'tab2', component: Tab2 },
],
},
],
})
export default router
3.2 动态路由匹配
动态路由:把Hash地址中可变的部分定义为参数项,从而提高路由规则的复用性。
- 在vue-router中使用英文的冒号(:)来定义路由的参数项。
- ① 通过动态路由匹配的方式渲染出来的组件中,可以使用
$route.params
对象访问到动态匹配的参数值。【记住路由规则里的参数名称,因为必须用$route.params.参数名称
来获取参数值】 - ② 为了简化路由参数的获取方式,
vue-rouetr
允许在路由规则中开启props传参。
- ① 通过动态路由匹配的方式渲染出来的组件中,可以使用
// router.js 关键部分代码
// 动态路由匹配
// 声明props:true选项,即可在MyMovie组件中,以props形式接收到路由规则匹配到的参数项
{ path: '/movie/:mid', component: Movie, props: true },
<!--App.vue 关键部分代码-->
<router-link to="/movie/1">电影1</router-link>
<router-link to="/movie/2">电影2</router-link>
<router-link to="/movie/3">电影3</router-link>
<!-- MyMovie.vue -->
<template>
<div>
<!-- 开启props传参后,可以直接使用props接收路由参数 -->
<h3>MyMovie 组件 --- {{ $route.params.mid }} --- {{ mid }}</h3>
<button type="button" class="btn btn-danger" @click="goBack">后退</button>
</div>
</template>
<script>
export default {
name: 'MyMovie',
props: ['mid'], // 使用props接收路由规则中匹配到的参数项
methods: {
goBack() {
this.$router.go(-1) // 后退到之前的组件按钮
},
},
}
</script>
三、编程式导航
3.1 编程式导航 vs 声明式导航
- 编程式导航:通过调用API实现导航的方式。
- 在普通网页中调用location.href跳转到新页面的方式属于编程式导航。
- 声明式导航:通过点击链接实现导航的方式。
- 在普通网页中点击<a>链接、vue项目中点击<router-link>都属于声明式导航。
3.2 vue-router中的编程式导航API
- 跳转到指定Hash地址,从而展示对应组件:
this.$router.push('hash地址')
- 实现导航历史的前进后退:
this.$router.go(整数值)
<!-- MyHome.vue -->
<template>
<div>
<h3>MyHome 组件</h3>
<button type="button" class="btn btn-primary" @click="goToMovie(3)">导航到Movie页面</button>
</div>
</template>
<script>
export default {
name: 'MyHome',
methods: {
goToMovie(id) {
this.$router.push('/movie/' + id) // 跳转到指定Hash地址
},
},
}
</script>
四、命名路由
命名路由:通过name属性为路由规则定义名称。
- 注意:命名路由的name值不能重复,必须保证唯一性!
- 使用命名路由实现声明式导航:为<router-link>标签动态绑定to属性的值,并通过name属性指定要跳转到的路由规则。期间还可以使用params属性指定跳转期间要携带的路由参数。
- 使用命名路由实现编程式导航:调用push函数期间制定一个配置对象,name是要跳转到的路由规则,params是携带的路由参数。
// router.js 关键部分代码
// 命名路由
{ name: 'mov', path: '/movie/:mid', component: Movie, props: true },
<!--MyHome.vue-->
<template>
<div>
<h3>MyHome 组件</h3>
<router-link :to="{ name: 'mov', params: { mid: 2 } }">go to movie</router-link>
<button type="button" class="btn btn-primary" @click="goToMovie(1)">go to movie</button>
</div>
</template>
<script>
export default {
name: 'MyHome',
methods: {
goToMovie(id) {
this.$router.push({
name: 'mov',
params: {
mid: id,
},
})
},
},
}
</script>
五、导航守卫
导航守卫可以控制路由的访问权限。
5.1 声明全局导航守卫
全局导航守卫会拦截每个路由规则,从而对每个路由进行访问权限的控制。
- 在守卫方法中如果不声明next形参,则默认允许用户访问每一个路由!
- 在守卫方法中如果声明了next形参,则必须调用next()函数,否则不允许用户访问任何一个路由!
5.2 next函数的三种调用方式:
- 直接放行:next()
- 强制其停留在当前页面:next(false)
- 强制其跳转到登录页面:next('/login')
// router.js 部分关键代码
// 声明全局的导航守卫
// 三个可选参数:to目标路由对象; from当前导航正要离开的路由对象; next是一个函数,表示放行
router.beforeEach((to, from, next) => {
const tokenStr = localStorage.getItem('token') // 1、读取token
if (to.path === '/main' && !tokenStr) { // 2、想访问“后台主页”,且token值不存在
// next(false) // 3.1 不允许跳转
next('/login') // 3.2 强制跳转到“登录页面”
} else {
next() // 3.3 直接放行,允许访问后台主页
}
})
六、后台管理案例
知识点:命名路由、路由重定向、导航守卫、嵌套路由、动态路由匹配、编程式导航
- main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import './assets/css/bootstrap.css'
// 1、导入路由模块
import router from './router'
const app=createApp(App)
//2、挂载路由对象
app.use(router)
app.mount('#app')
- router.js
//1、按需导入对应的函数
import { createRouter, createWebHashHistory } from "vue-router";
import Login from './components/MyLogin.vue'
import Home from './components/MyHome.vue'
import Users from './components/menus/MyUsers.vue'
import Rights from './components/menus/MyRights.vue'
import Goods from './components/menus/MyGoods.vue'
import Orders from './components/menus/MyOrders.vue'
import Settings from './components/menus/MySettings.vue'
import UserDetail from './components/user/MyUserDetail.vue'
//2、创建路由对象
const router=createRouter({
history:createWebHashHistory(),
routes:[
// 路由重定向
{path:'/',redirect:'/login'},
{path:'/login',component:Login},
{
path:'/home',
component:Home,
// 用户访问/home时,重定向到/home/users
redirect:'/home/users',
// 子路由规则
children:[
{path:'users',component:Users},
{path:'rights',component:Rights},
{path:'goods',component:Goods},
{path:'orders',component:Orders},
{path:'settings',component:Settings},
// 用户详情页的动态路由规则
{path:'users/:id',component:UserDetail,props:true},
]
},
],
})
// 全局路由导航守卫
router.beforeEach((to,from,next)=>{
// 若用户访问的是登录页面,直接放行
if(to.path==='/login') return next()
// 获取Token值
const token=localStorage.getItem('token')
if(!token){ // 若Token值不存在,强致跳转登录页面
return next('/login')
}
next() // 存在token值,直接放行
})
//3、向外共享路由实例对象
export default router
- index.css
:root {
font-size: 13px;
}
html,
body,
#app {
height: 100%;
}
- App.vue
<template>
<!-- 路由占位符 -->
<router-view></router-view>
</template>
<script>
export default {
name: 'MyApp',
}
</script>
- /components/MyLogin.vue
<template>
<div class="login-container">
<div class="login-box">
<!-- 头像区域 -->
<div class="text-center avatar-box">
<img src="../assets/logo.png" class="img-thumbnail avatar" alt="" />
</div>
<!-- 表单区域 -->
<div class="form-login p-4">
<!-- 登录名称,文本框使用v-model双向数据绑定 -->
<div class="form-group form-inline">
<label for="username">登录名称</label>
<input
type="text"
class="form-control ml-2"
id="username"
placeholder="请输入登录名称"
autocomplete="off"
v-model.trim="username"
/>
</div>
<!-- 登录密码,文本框使用v-model双向数据绑定 -->
<div class="form-group form-inline">
<label for="password">登录密码</label>
<input
type="password"
class="form-control ml-2"
id="password"
placeholder="请输入登录密码"
v-model.trim="password"
/>
</div>
<!-- 登录和重置按钮 -->
<div class="form-group form-inline d-flex justify-content-end">
<button type="button" class="btn btn-secondary mr-2">重置</button>
<button type="button" class="btn btn-primary" @click="onLoginClick">登录</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default{
name: 'MyLogin',
data(){
return{
username:'',
password:'',
}
},
methods:{
onLoginClick(){
//判断用户名和密码是否正确
if (this.username === 'admin' && this.password === '123456') {
//登录成功,跳转到后台主页
this.$router.push('/home')
//模拟存储Token操作
localStorage.setItem('token', 'Bearer xxx')
} else {
//登录成功,清除Token
localStorage.removeItem('token')
}
},
}
}
</script>
<style lang="less" scoped>
.login-container {
background-color: #35495e;
height: 100%;
.login-box {
width: 400px;
height: 250px;
background-color: #fff;
border-radius: 3px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 6px rgba(255, 255, 255, 0.5);
.form-login {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
box-sizing: border-box;
}
}
}
.form-control {
flex: 1;
}
.avatar-box {
position: absolute;
width: 100%;
top: -65px;
left: 0;
.avatar {
width: 120px;
height: 120px;
border-radius: 50% !important;
box-shadow: 0 0 6px #efefef;
}
}
</style>
- /components/MyHome.vue
<template>
<div class="home-container">
<!-- 头部区域 -->
<my-header></my-header>
<!-- 主体区域 -->
<div class="home-main-box">
<!-- 左侧边栏区域 -->
<my-aside></my-aside>
<!-- 右侧内容主体区域 -->
<div class="home-main-body">
<!-- ***子路由占位符*** -->
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
// 头部区域组件
import MyHeader from './subcomponents/MyHeader.vue'
// 左侧边栏组件
import MyAside from './subcomponents/MyAside.vue'
export default {
name: 'MyHome',
components: {
MyHeader,
MyAside,
}
}
</script>
<style lang="less" scoped>
.home-container {
height: 100%;
display: flex;
flex-direction: column;
.home-main-box {
height: 100%;
display: flex;
.home-main-body {
padding: 15px;
flex: 1;
}
}
}
</style>
- /components/subcomponents/MyHeader.vue
<template>
<div class="layout-header-container d-flex justify-content-between align-items-center p-3">
<!-- 左侧 logo 和 标题区域 -->
<div class="layout-header-left d-flex align-items-center user-select-none">
<!-- logo -->
<img class="layout-header-left-img" src="../../assets/logo.png" alt="">
<!-- 标题 -->
<h4 class="layout-header-left-title ml-3">后台管理系统</h4>
</div>
<!-- 右侧按钮区域 -->
<div class="layout-header-right">
<button type="button" class="btn btn-light" @click="onLogout">退出登录</button>
</div>
</div>
</template>
<script>
export default {
name: 'MyHeader',
methods: {
// 退出按钮的点击事件处理函数
onLogout() {
//移除Token
localStorage.removeItem('token')
//强制跳转到登录页面
this.$router.push('/login')
}
}
}
</script>
<style lang="less" scoped>
.layout-header-container {
height: 60px;
border-bottom: 1px solid #eaeaea;
}
.layout-header-left-img {
height: 50px;
}
</style>
-
/components/subcomponents/MyAside.vue:略,只有样式,没有路由相关操作。
-
/components/menus/MyUsers.vue
<template>
<!-- 标题 -->
<h4 class="text-center">用户管理</h4>
<!-- 用户列表 -->
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>#</th>
<th>姓名</th>
<th>年龄</th>
<th>头衔</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, i) in userlist" :key="item.id">
<td>{{ i + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.position }}</td>
<td>
<router-link :to="'/home/users/' + item.id">详情</router-link>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'MyUser',
data() {
return {
// 用户列表数据
userlist: [
{ id: 1, name: '嬴政', age: 18, position: '始皇帝' },
{ id: 2, name: '李斯', age: 35, position: '丞相' },
{ id: 3, name: '吕不韦', age: 50, position: '商人' },
{ id: 4, name: '赵姬', age: 48, position: '王太后' },
],
}
},
}
</script>
<style lang="less" scoped></style>
-
/components/menus/MyGoods.vue:略,只有样式,没有路由相关操作。
-
/components/menus/MyOrders.vue:略,只有样式,没有路由相关操作。
-
/components/menus/MyRights.vue:略,只有样式,没有路由相关操作。
-
/components/menus/MySettings.vue:略,只有样式,没有路由相关操作。
-
/components/user/MyUserDetail.vue
<template>
<button type="button" class="btn btn-light btn-sm" @click="goBack">后退</button>
<h4 class="text-center">用户详情 --- {{id}}</h4>
</template>
<script>
export default {
name: 'MyUserDetail',
props: ['id'],
methods: {
// 编程式导航实现后退功能
goBack() {
this.$router.go(-1)
}
}
}
</script>
<style lang="less" scoped>
</style>
参考: https://blog.csdn.net/SongD1114/article/details/124055153
https://blog.csdn.net/Gik99/article/details/129368630
https://blog.csdn.net/2301_77638787/article/details/132318329