【VUE】day07 路由
1. 路由
在 Vue.js 中,路由(Routing)是通过 Vue Router
实现的。Vue Router
是 Vue.js 官方提供的路由管理器,用于构建单页面应用程序(SPA)。它允许你在不重新加载页面的情况下,动态切换视图和组件,同时保持 URL 的同步。
在 Vue.js 中,路由(Routing)是通过 Vue Router 实现的。Vue Router 是 Vue.js 官方提供的路由管理器,用于构建单页面应用程序(SPA)。它允许你在不重新加载页面的情况下,动态切换视图和组件,同时保持 URL 的同步。
总体来说,可以实现以下的功能
-
通过URL来映射不同的组件,实现页面切换;
-
支持动态路由,使 URL 传递参数;
-
提供嵌套路由,实现多级路由嵌套;
-
支持编程式导航,可以通过 JavaScript 代码进行跳转;
-
提供路由守卫,控制页面访问权限。
核心概念
1. 路由(Route)
路由定义了 URL 和组件之间的映射关系。当用户访问某个 URL 时,Vue Router 会根据路由配置加载相应的组件。
2. 路由表(Routes)
路由表是一个数组,包含多个路由配置。每个路由配置通常包括 path(路径)、component(组件)等属性。
3. 路由器(Router)
路由器是 Vue Router 的核心实例,负责管理路由表和处理路由切换。
4. 路由视图(RouterView)
<router-view>
是一个占位符组件,用于渲染当前路由匹配的组件。
5. 路由链接(RouterLink)
<router-link>
是一个导航组件,用于生成导航链接,点击后会切换到对应的路由。
6. 动态路由
动态路由允许在路径中使用参数(如 /user/:id),这些参数可以在组件中通过 this.$route.params 访问。
7. 嵌套路由
嵌套路由允许在父路由中定义子路由,用于构建复杂的页面结构。
8. 路由守卫(Navigation Guards)
路由守卫是钩子函数,用于在路由切换前后执行某些操作,例如权限验证、数据加载等。
2. 前端路由的工作方式
3. 实现简易的前端路由
但实际应该动态的显示组件
<template>
<div class="app-container">
<h1>App 根组件</h1>
<a href="#/home">首页</a>
<a href="#/movie">电影</a>
<a href="#/about">关于</a>
<hr />
<component :is="comName"></component>
</div>
</template>
<script>
// 导入组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
export default {
name: 'App',
data(){
return{
comName: 'Home'
}
},
created(){
window.onhashchange = () =>{
console.log('监听到hash地址的变化',location.hash)
switch(location.hash){
case '#/home':
this.comName = 'Home'
break
case '#/movie':
this.comName = 'Movie'
break
case '#/about':
this.comName = 'About'
break
}
}
},
// 注册组件
components: {
Home,
Movie,
About
}
}
</script>
<style lang="less" scoped>
.app-container {
background-color: #efefef;
overflow: hidden;
margin: 10px;
padding: 15px;
> a {
margin-right: 10px;
}
}
</style>
4. 安装和配置路由
vue-router
安装和配置的步骤。
① 安装vue-router
包
② 创建路由模块
③ 导入并挂在路由模块
④ 声明路由链接和占位符
4.1 安装vue-router
包
在vue2的项目中安装vue-router的命令如下:
npm i vue-router@3.5.2 -S
4.2 创建路由模块
在src源代码目录下,新建router/index.js
路由模块,并初始化如下的代码:
// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'
import Login from '@/components/Login.vue'
import Main from '@/components/Main.vue'
// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)
// 创建路由的实例对象
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
// 重定向的路由规则
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
// 需求:在 Movie 组件中,希望根据 id 的值,展示对应电影的详情信息
// 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:mid', component: Movie, props: true },
{
path: '/about',
component: About,
// redirect: '/about/tab1',
children: [
// 子路由规则
// 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
},
{ path: '/login', component: Login },
{ path: '/main', component: Main }
]
})
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
export default router
4.3 导入并挂在路由模块
import Vue from 'vue'
import App from './App2.vue'
// 导入路由模块,目的:拿到路由的实例对象
// 在进行模块化导入的时候,如果给定的是文件夹,则默认导入这个文件夹下,名字叫做 index.js 的文件
import router from '@/router/index.js'
// 导入 bootstrap 样式
import 'bootstrap/dist/css/bootstrap.min.css'
// 全局样式
import '@/assets/global.css'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
// 在 Vue 项目中,要想把路由用起来,必须把路由实例对象,通过下面的方式进行挂载
// router: 路由的实例对象
router
}).$mount('#app')
5. 在路由模块中声明路由的对应关系
5.1 router-view
<router-view>
是 Vue Router 提供的核心组件之一,用于渲染匹配当前路由的组件。它是构建单页面应用(SPA)的关键元素,负责根据路由配置动态加载和显示不同的视图组件。 以下是关于 <router-view>
的详细介绍:
一、<router-view>
的作用与功能
1.动态渲染组件
<router-view>
是一个占位符组件,会根据当前路由路径(URL)自动渲染匹配的组件。例如,当用户访问 /home 时,<router-view>
会渲染 Home 组件。
2.支持嵌套路由
在嵌套路由中,<router-view>
可以嵌套使用,用于渲染子路由对应的组件。例如,父路由 /user 中嵌套子路由 /user/profile,子组件会渲染在父组件的 中。
3.过渡动画
<router-view>
可以与 Vue 的 <transition>
组件结合,为路由切换添加动画效果,提升用户体验。
4,命名视图
支持多视图渲染,通过 name 属性指定不同的视图出口。例如,主视图和侧边栏视图可以分别渲染在不同的 <router-view>
中。
二、基本用法
1.简单示例
在 App.vue 中使用 <router-view>
渲染匹配的组件:
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view></router-view> <!-- 渲染匹配的组件 -->
</div>
</template>
2.嵌套路由示例
在父组件中嵌套 <router-view>
渲染子路由:
<template>
<div>
<h2>User Page</h2>
<router-link to="/user/profile">Profile</router-link>
<router-link to="/user/posts">Posts</router-link>
<router-view></router-view> <!-- 渲染子路由组件 -->
</div>
</template>
3.过渡动画示例
为路由切换添加淡入淡出效果:
<template>
<div id="app">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</div>
</template>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
其他例子
APP.vue
<template>
<div class="app-container">
<h1>App2 组件</h1>
<!-- 当安装和配置了 vue-router 后,就可以使用 router-link 来替代普通的 a 链接了 -->
<!-- <a href="#/home">首页</a> -->
<router-link to="/home">首页</router-link>
<!-- 注意1:在 hash 地址中, / 后面的参数项,叫做“路径参数” -->
<!-- 在路由“参数对象”中,需要使用 this.$route.params 来访问路径参数 -->
<!-- 注意2:在 hash 地址中,? 后面的参数项,叫做“查询参数” -->
<!-- 在路由“参数对象”中,需要使用 this.$route.query 来访问查询参数 -->
<!-- 注意3:在 this.$route 中,path 只是路径部分;fullPath 是完整的地址 -->
<!-- 例如: -->
<!-- /movie/2?name=zs&age=20 是 fullPath 的值 -->
<!-- /movie/2 是 path 的值 -->
<router-link to="/movie/1">洛基</router-link>
<router-link to="/movie/2?name=zs&age=20">雷神</router-link>
<router-link to="/movie/3">复联</router-link>
<router-link to="/about">关于</router-link>
<hr />
<!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
<!-- 它的作用很单纯:占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style lang="less" scoped>
.app-container {
background-color: #efefef;
overflow: hidden;
margin: 10px;
padding: 15px;
> a {
margin-right: 10px;
}
}
</style>
// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'
import Login from '@/components/Login.vue'
import Main from '@/components/Main.vue'
// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)
// 创建路由的实例对象
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
// 重定向的路由规则
{ path: '/', redirect: '/home' },
// 路由规则,把#去掉
{ path: '/home', component: Home },
// 需求:在 Movie 组件中,希望根据 id 的值,展示对应电影的详情信息
// 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:mid', component: Movie, props: true },
{
path: '/about',
component: About,
// redirect: '/about/tab1',
children: [
// 子路由规则
// 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
},
{ path: '/login', component: Login },
{ path: '/main', component: Main }
]
})
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
export default router
6. 嵌套路由
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
// 重定向的路由规则
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
// 需求:在 Movie 组件中,希望根据 id 的值,展示对应电影的详情信息
// 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:mid', component: Movie, props: true },
{
path: '/about',
component: About,
// redirect: '/about/tab1',
children: [
// 子路由规则
// 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
},
{ path: '/login', component: Login },
{ path: '/main', component: Main }
]
})
About.vue
<template>
<div class="about-container">
<h3>About 组件</h3>
<!-- 子级路由链接 -->
<router-link to="/about">tab1</router-link>
<router-link to="/about/tab2">tab2</router-link>
<hr />
<!-- 子级路由占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'About'
}
</script>
<style lang="less" scoped>
.about-container {
min-height: 200px;
background-color: skyblue;
padding: 15px;
> a {
margin-right: 10px;
}
}
</style>
Tab1.vue
<template>
<div class="tab1-container">
<h5>Tab1 组件</h5>
</div>
</template>
<script>
export default {
name: 'Tab1'
}
</script>
<style lang="less" scoped>
.tab1-container {
min-height: 150px;
background-color: greenyellow;
}
</style>
其中的子路由就是嵌套链接
7.动态路由
动态路由指的是:把Hash地址中可变的部分定义为参数项,从而提高路由规则的复用性。
在vue-router中使用英文的冒号
:
来定义路由的参数项。示例代码如下:
<template>
<div class="app-container">
<h1>App2 组件</h1>
<!-- 当安装和配置了 vue-router 后,就可以使用 router-link 来替代普通的 a 链接了 -->
<!-- <a href="#/home">首页</a> -->
<router-link to="/home">首页</router-link>
<!-- 注意1:在 hash 地址中, / 后面的参数项,叫做“路径参数” -->
<!-- 在路由“参数对象”中,需要使用 this.$route.params 来访问路径参数 -->
<!-- 注意2:在 hash 地址中,? 后面的参数项,叫做“查询参数” -->
<!-- 在路由“参数对象”中,需要使用 this.$route.query 来访问查询参数 -->
<!-- 注意3:在 this.$route 中,path 只是路径部分;fullPath 是完整的地址 -->
<!-- 例如: -->
<!-- /movie/2?name=zs&age=20 是 fullPath 的值 -->
<!-- /movie/2 是 path 的值 -->
<router-link to="/movie/1">洛基</router-link>
<router-link to="/movie/2?name=zs&age=20">雷神</router-link>
<router-link to="/movie/3">复联</router-link>
<router-link to="/about">关于</router-link>
<hr />
<!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
<!-- 它的作用很单纯:占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style lang="less" scoped>
.app-container {
background-color: #efefef;
overflow: hidden;
margin: 10px;
padding: 15px;
> a {
margin-right: 10px;
}
}
</style>
// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'
import Login from '@/components/Login.vue'
import Main from '@/components/Main.vue'
// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)
// 创建路由的实例对象
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
// 重定向的路由规则
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
// 需求:在 Movie 组件中,希望根据 id 的值,展示对应电影的详情信息
// 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:mid', component: Movie, props: true },
{
path: '/about',
component: About,
// redirect: '/about/tab1',
children: [
// 子路由规则
// 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
},
{ path: '/login', component: Login },
{ path: '/main', component: Main }
]
})
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
export default router
<template>
<div class="movie-container">
<!-- this.$route 是路由的“参数对象” -->
<!-- this.$router 是路由的“导航对象” -->
<h3>Movie 组件 --- {{ $route.params.mid }} --- {{ mid }}</h3>
<button @click="showThis">打印 this</button>
<button @click="goback">后退</button>
<!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错! -->
<button @click="$router.back()">back 后退</button>
<button @click="$router.forward()">forward 前进</button>
</div>
</template>
<script>
export default {
name: 'Movie',
// 接收 props 数据
props: ['mid'],
methods: {
showThis() {
console.log(this)
},
goback() {
// go(-1) 表示后退一层
// 如果后退的层数超过上限,则原地不动
this.$router.go(-1)
}
}
}
</script>
<style lang="less" scoped>
.movie-container {
min-height: 200px;
background-color: lightsalmon;
padding: 15px;
}
</style>
8. 导航
8.1 声明式导航
在浏览器中,点击链接
实现导航的方式,叫做声明式导航
。例如:
- 普通网页中点击
<a>链接
、vue项目中点击<router-link>
都属于声明式导航。
8.2 编程式导航
在浏览器中,调用API方法实现导航的方式叫做编程式导航
。例如:
- 普通网页中调用location.href跳转到新页面的方式,属于编程式导航。
8.3 vue-router中的编程式导航API
vue-router提供了许多编程式导航的API,其中最常用的导航API是:
this.$router.push('hash地址
) 跳转到指定的hash地址,并增加一条历史记录this.$router.replace('hash地址')
跳转到指定的hash地址,并替换掉当前的历史记录this.$router.go(数值n)
<template>
<div class="home-container">
<h3>Home 组件</h3>
<hr />
<button @click="gotoLk">通过 push 跳转到“洛基”页面</button>
<button @click="gotoLk2">通过 replace 跳转到“洛基”页面</button>
<router-link to="/main">访问后台主页</router-link>
</div>
</template>
<script>
export default {
name: 'Home',
methods: {
gotoLk() {
// 通过编程式导航 API,导航跳转到指定的页面
this.$router.push('/movie/1')
},
gotoLk2() {
this.$router.replace('/movie/1')
}
}
}
</script>
<style lang="less" scoped>
.home-container {
min-height: 200px;
background-color: pink;
padding: 15px;
}
</style>
this.$router.go(数值n)
可以在浏览历史中前进和后退,示例代码那如下
8.3.1 $router.go
的简化用法
在实际开发中,一般只会前进和后退一层页面,因此vue-router提供了如下两个便捷方法:
①router.back()
②$router.forward()
8.4 导航守卫的基本用法
导航守卫可以控制路由的访问权限。 示意图如下:
8.4.1 全局前置守卫
每次发生路由的导航跳转
时,都会触发全局前置守卫
。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限
的控制:
// src/router/index.js 就是当前项目的路由模块
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'
import Login from '@/components/Login.vue'
import Main from '@/components/Main.vue'
// 把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 函数的作用,就是来安装插件的
Vue.use(VueRouter)
// 创建路由的实例对象
const router = new VueRouter({
// routes 是一个数组,作用:定义 “hash 地址” 与 “组件” 之间的对应关系
routes: [
// 重定向的路由规则
{ path: '/', redirect: '/home' },
// 路由规则
{ path: '/home', component: Home },
// 需求:在 Movie 组件中,希望根据 id 的值,展示对应电影的详情信息
// 可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:mid', component: Movie, props: true },
{
path: '/about',
component: About,
// redirect: '/about/tab1',
children: [
// 子路由规则
// 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
},
{ path: '/login', component: Login },
{ path: '/main', component: Main }
]
})
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
export default router
8.4.1.1 next函数的3种调用方式
9. 路由项目
main.js
import Vue from 'vue'
import App from './App.vue'
// 导入路由模块
import router from '@/router'
// 导入样式
import './assets/css/bootstrap.css'
import './index.css'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router
}).$mount('#app')
index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import pathArr from '@/router/pathArr.js'
// 导入需要的组件
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'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/login' },
// 登录的路由规则
{ path: '/login', component: Login },
// 后台主页的路由规则
{
path: '/home',
component: Home,
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: 'userinfo/:id', component: UserDetail, props: true }
]
}
]
})
// 全局前置守卫
router.beforeEach(function(to, from, next) {
if (pathArr.indexOf(to.path) !== -1) {
const token = localStorage.getItem('token')
if (token) {
next()
} else {
next('/login')
}
} else {
next()
}
})
export default router
App.vue
<template>
<!-- 占位符 -->
<router-view></router-view>
</template>
<script>
export default {
name: 'MyApp'
}
</script>
<style lang="less" scoped></style>
9.1 完整代码
index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import pathArr from '@/router/pathArr.js'
// 导入需要的组件
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'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/login' },
// 登录的路由规则
{ path: '/login', component: Login },
// 后台主页的路由规则
{
path: '/home',
component: Home,
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: 'userinfo/:id', component: UserDetail, props: true }
]
}
]
})
// 全局前置守卫
router.beforeEach(function(to, from, next) {
if (pathArr.indexOf(to.path) !== -1) {
const token = localStorage.getItem('token')
if (token) {
next()
} else {
next('/login')
}
} else {
next()
}
})
export default router
pathArr.js
export default ['/home', '/home/users', '/home/rights']
main.js
import Vue from 'vue'
import App from './App.vue'
// 导入路由模块
import router from '@/router'
// 导入样式
import './assets/css/bootstrap.css'
import './index.css'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router
}).$mount('#app')
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">
<!-- 登录名称 -->
<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>
<!-- 登录密码 -->
<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" @click="reset">重置</button>
<button type="button" class="btn btn-primary" @click="login">登录</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'MyLogin',
data() {
return {
username: '',
password: ''
}
},
methods: {
reset() {
this.username = ''
this.password = ''
},
login() {
if (this.username === 'admin' && this.password === '666666') {
// 登录成功
// 1. 存储 token
localStorage.setItem('token', 'Bearer xxxx')
// 2. 跳转到后台主页
this.$router.push('/home')
} else {
// 登录失败
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>
MyHome.vue
<template>
<div class="home-container">
<!-- 头部区域 -->
<MyHeader></MyHeader>
<!-- 页面主体区域 -->
<div class="home-main-box">
<!-- 左侧边栏 -->
<MyAside></MyAside>
<!-- 右侧内容主体区域 -->
<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>
MyUserDetail.vue
<template>
<div>
<button type="button" class="btn btn-light btn-sm" @click="$router.back()">后退</button>
<h4 class="text-center">用户详情 --- {{ id }}</h4>
</div>
</template>
<script>
export default {
name: 'MyUserDetail',
props: ['id']
}
</script>
<style lang="less" scoped></style>
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/heima.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="logout">退出登录</button>
</div>
</div>
</template>
<script>
export default {
name: 'MyHeader',
methods: {
logout() {
// 1. 清空 token
localStorage.removeItem('token')
// 2. 跳转到登录页面
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>
MyAside.vue
<template>
<div class="layout-aside-container">
<!-- 左侧边栏列表 -->
<ul class="user-select-none menu">
<li class="menu-item">
<router-link to="/home/users">用户管理</router-link>
</li>
<li class="menu-item">
<router-link to="/home/rights">权限管理</router-link>
</li>
<li class="menu-item">
<router-link to="/home/goods">商品管理</router-link>
</li>
<li class="menu-item">
<router-link to="/home/orders">订单管理</router-link>
</li>
<li class="menu-item">
<router-link to="/home/settings">系统设置</router-link>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'MyAside'
}
</script>
<style lang="less" scoped>
.layout-aside-container {
width: 250px;
height: 100%;
border-right: 1px solid #eaeaea;
}
.menu {
list-style-type: none;
padding: 0;
.menu-item {
line-height: 50px;
font-weight: bold;
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
'Helvetica Neue', sans-serif;
&:hover {
background-color: #efefef;
cursor: pointer;
}
a {
display: block;
color: black;
padding-left: 30px;
&:hover {
text-decoration: none;
}
}
}
}
// 设置路由高亮效果
.router-link-active {
background-color: #efefef;
box-sizing: border-box;
position: relative;
// 伪元素实现路由高亮效果
&::before {
content: ' ';
display: block;
width: 4px;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: #42b983;
}
}
</style>
MyUsers.vue
<template>
<div>
<!-- 标题 -->
<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 in userlist" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.position }}</td>
<td>
<a href="#" @click.prevent="gotoDetail(item.id)">详情</a>
</td>
</tr>
</tbody>
</table>
</div>
</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: '王太后' }
]
}
},
methods: {
gotoDetail(id) {
// /home/userinfo/1
// /home/userinfo/2
// /home/userinfo/3
this.$router.push('/home/userinfo/' + id)
}
}
}
</script>
<style lang="less" scoped></style>