目录
一、引入 vue-router 进行路由管理
1、简单了解下
之前在 搭建基本页面时,已经简单使用过,这里再深入了解一下。
1)文件格式如下
由于创建项目时,指定了 router,所以 vue-cli 自动生成了 router 文件夹以及相关的 js 文件。
2)手动引入 router(可选操作)。
若初始化项目时未指定 router,可以自己手动添加 router。
【router 中文文档:】 https://router.vuejs.org/zh/
2、项目中使用
(1)简介
此项目是单页面应用,通过 vue-router 将 各个组件(components)映射到指定位置,实现页面切换的效果。
之前定义基本页面时,已经简单应用了 router。
(2)代码如下:
根据路径可以进行路由匹配,也可根据 name 属性去定位路由。
其中:
component 采用路由懒加载的形式( () => import() ),路由被访问时再加载。
path: '/' 表示项目根路径。
redirect 表示跳转到另一个路由。
name: "Login" 表示路由名,可以根据 name 定位路由。
path: "*" 表示全匹配,一般写在路由规则的最后一个(用于路径不存在时跳转到一个指定页面)。
import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
Vue.use(Router);
const routes = [{
path: "/",
name: "home",
component: Home,
children: [{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import( /* webpackChunkName: "about" */ "./views/About.vue")
}, ]
},
{
path: "/login",
name: "Login",
component: () => import("./views/login.vue")
},
{
path: "*",
name: "404",
component: () => import('./views/404.vue')
},
]
const router = new Router({
routes, // 用于定义 路由跳转 规则
mode: "history", // mode 用于去除地址中的 #
base: process.env.BASE_URL,
scrollBehavior: () => ({ // scrollBehavior 用于定义路由切换时,页面滚动。
y: 0
})
});
// 解决相同路径跳转报错
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => {
err
})
}
export default router
3、导航守卫、路由元信息
(1)简介
导航守卫适用于 路由变化时。即当路由变化时,会触发导航守卫。
路由元信息 可以用于定义路由独有的信息(meta)。
注: 同一个组件切换时,参数改变不会触发导航守卫(复用组件)。可以通过 watch 监听 $route 对象的变化来定义导航守卫,或者 直接使用 beforeRouteUpdate 来进行导航守卫(组件内守卫)。
(2)全局前置守卫(beforeEach)
使用 beforeEach 可以定义一个全局前置守卫,路由跳转前会触发。
其有三个参数:
to:一个路由对象,表示即将进入的 目标路由对象。
from:一个路由对象,表示当前路由 离开时的路由对象。
next:一个方法(不能少,确保路由能够跳转出去。
next() 表示执行下一个守卫规则,若所有规则执行完毕,则结束并跳转到指定路由。
next({path: ''}) 或者 next({name: ''}) 表示指定路径跳转。
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
(3)路由元信息
定义路由规则时,可以通过 meta 指定路由的元信息。
通过 router对象.meta 可以获取到某个 router对象 的 meta 信息,并根据其进行处理。
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, meta: { requiresAuth: true } } ] } ] })
(4)项目中使用
进入主页面后,当 token 过期或不存在时,需要跳转到登录页面重新登录。
使用 导航守卫,每次路由跳转前,确定 token 是否存在。可以使用 beforeEach 定义全局守卫,也可以使用 beforeEnter 为某个路由定义独有守卫。
此处演示使用 beforeEach 定义全局守卫。
Step1:
修改 Login.vue 登录逻辑,保存 token 值。
之前将 cookie 相关的操作保存在 /utils/auth.js 中,需要引入该 js。
import { setToken } from '@/utils/auth.js';
// 提交表单
submitForm() {
// 登录
this.$request({
url: '/auth/token',
method: 'post',
data: this.loginForm,
}).then((res) => {
if (res.data.code === 0) {
this.$message({ message: '登录成功', type: 'success' });
// 保存 token 值
setToken(res.data.token);
this.updateName(this.loginForm.username);
this.$router.push({
name: 'About',
});
}
});
},
完整代码:
<div class="login" :style="{ backgroundImage: 'url(' + loginBg + ')' }">
<div class="wrapper">
<h3 class="head">CMS后台管理系统</h3>
<el-form :rules="rules" size="medium" :model="loginForm" ref="loginForm" class="login-form" @keyup.enter.native="submitForm">
<el-form-item prop="userName">
<el-input size="medium" type="text" v-model.trim="loginForm.userName" placeholder="请输入用户名" autocomplete="off"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input size="medium" type="password" v-model.trim="loginForm.password" placeholder="请输入密码" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button size="medium" style="width: 100%" type="primary" @click="submitForm">立即登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { setToken } from '@/utils/auth.js';
import { mapActions } from 'vuex';
export default {
data() {
return {
loginBg: require('../assets/images/login-bg.png'),
loginForm: { userName: '', password: '' },
rules: {
userName: [{ required: 'true', message: '账户不能为空', trigger: 'blur' }],
password: [{ required: 'true', message: '密码不能为空', trigger: 'blur' }],
},
};
},
methods: {
...mapActions('user', ['updateName']),
submitForm() {
// 登录
this.$request({
url: '/auth/token',
method: 'post',
data: this.loginForm,
}).then((res) => {
if (res.data.code === 0) {
this.$message({ message: '登录成功', type: 'success' });
// 保存 token 值
setToken(res.data.token);
this.updateName(this.loginForm.userName);
this.$router.push({
name: 'About',
});
}
});
},
},
};
</script>
<style scoped>
.login {
height: 100vh;
background-size: 100% 100%;
}
.wrapper {
position: absolute; /* fixed 同理 */
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 20rem;
}
.head {
margin-bottom: 25px;
color: #fff;
font-size: 24px;
}
</style>
Step2:
修改路由。
定义路由元信息(meta)。meta 用于定义路由元信息,其中 isRouter 用于指示是否开启路由守卫(true 表示开启)。并按照aside.vue新增路由及文件Echart.vue及Ueditor.vue
const routes = [{
path: "/",
name: "Home",
component: Home,
children: [{
path: "/about",
name: "About",
meta: {
name: '关于我们',
isRouter: true
},
component: () =>
import( /* webpackChunkName: "about" */ "./views/About.vue")
},
{
path: '/demo/echart',
name: 'Echarts',
component: () => import('@/views/echart/Echarts.vue'),
meta: {
isRouter: true
}
},
{
path: '/demo/ueditor',
name: 'Ueditor',
component: () => import('@/views/ueditor/Ueditor.vue'),
meta: {
isRouter: true
}
}
]
},
{
path: "*",
name: "404",
component: () => import("./views/404.vue")
},
{
path: "/login",
name: "Login",
component: () => import("./views/login.vue")
}
]
Step3:
添加全局守卫(beforeEach)。
当 isRouter 为 true 时,才会去校验 token,token 校验失败则跳转到 Login 页面重新登录。
// 添加全局路由导航守卫
router.beforeEach((to, from, next) => {
// 当开启导航守卫时,验证 token 是否存在。
if (to.meta.isRouter) {
// 获取 token 值
let token = getToken()
console.log(token)
// token 不存在时,跳转到 登录页面
if (!token || !/\S/.test(token)) {
next({name: 'Login'})
}
}
next()
})
Step4:
完整 router 如下:
import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import {
getToken
} from "@/utils/auth.js";
Vue.use(Router);
const routes = [{
path: "/",
name: "Home",
component: Home,
children: [{
path: "/about",
name: "About",
meta: {
name: '关于我们',
isRouter: true
},
component: () =>
import( /* webpackChunkName: "about" */ "./views/About.vue")
},
{
path: '/demo/echart',
name: 'Echarts',
component: () => import('@/views/echart/Echarts.vue'),
meta: {
isRouter: true
}
},
{
path: '/demo/ueditor',
name: 'Ueditor',
component: () => import('@/views/ueditor/Ueditor.vue'),
meta: {
isRouter: true
}
}
]
},
{
path: "*",
name: "404",
component: () => import("./views/404.vue")
},
{
path: "/login",
name: "Login",
component: () => import("./views/login.vue")
}
]
const router = new Router({
routes, // 用于定义 路由跳转 规则
mode: "history", // mode 用于去除地址中的 #
base: process.env.BASE_URL,
scrollBehavior: () => ({ // scrollBehavior 用于定义路由切换时,页面滚动。
y: 0
})
});
// 添加全局路由导航守卫
router.beforeEach((to, from, next) => {
// 当开启导航守卫时,验证 token 是否存在。
if (to.meta.isRouter) {
// 获取 token 值
let token = getToken()
console.log(token)
// token 不存在时,跳转到 登录页面
if (!token || !/\S/.test(token)) {
next({name: 'Login'})
}
}
next()
})
// 解决相同路径跳转报错
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => {
err
})
}
export default router
Step5:
测试一下。
手动模拟 token 失效。token 失效后,点击菜单栏,路由会跳转到 登录界面。
二、模块化封装 axios 请求
1、简介
前面封装了 axios,并在 main.js 中全局挂载,所以在组件中可以使用 $http 进行访问。
但是每次请求的相关处理都会写在各个组件中,代码看上去不太美观,且不易维护。
所以可以将请求根据功能、模块进行划分,并写在固定位置,在组件中引入这些模块即可。
2、代码实现
(1)按照功能将请求进行模块划分。
比如:登录、登出的请求为 login.js,用户信息相关的请求为 user.js,菜单相关的请求为 menu.js。
import request from '@/utils/request'
export function getToken(data) {
return request({
url: '/auth/token',
method: 'post',
data
})
}
(2)定义一个 index.js,引入 login.js 模块。
import * as login from './modules/login.js'
import * as user from './modules/user.js'
export default {
login,
user
}
(3)在 main.js 中全局挂载。
import request from '@/api/index.js'
Vue.prototype.$request = request
(4)修改 Login.vue 的登录逻辑,通过全局挂载的 $request调用 login 模块的 getToken 方法。
submitForm() {
// 登录
this.$request.login.getToken(this.loginForm).then((res) => {
if (res.data.code === 0) {
this.$message({ message: '登录成功', type: 'success' });
// 保存 token 值
setToken(res.data.token);
this.updateName(this.loginForm.userName);
this.$router.push({
name: 'About',
});
}
});
},