上一天的学习中,我们已经完成了登录的过程,并且存储了token,但是此时主页并没有根据token的有无而被控制访问权限
访问权限拦截的流程图如下所示:
在src/permission.js中添加权限拦截代码,使用了路由导航守卫功能:
// 权限拦截 导航守卫 路由守卫 router
import router from '@/router' // 引入路由实例
import store from '@/store' // 引入vuex store实例
import NProgress from 'nprogress' // 引入一份进度条插件
import 'nprogress/nprogress.css' // 引入进度条样式
const whiteList = ['/login', '/404'] // 定义白名单 所有不受权限控制的页面
// 路由的前置守卫
router.beforeEach(function(to, from, next) {
NProgress.start() // 开启进度条
// 首先判断有无token
if (store.getters.token) {
// 如果有token 继续判断是不是去登录页
if (to.path === '/login') {
// 表示去的是登录页
next('/') // 跳到主页
} else {
next() // 直接放行
}
} else {
// 如果没有token
if (whiteList.indexOf(to.path) > -1) {
// 如果找到了 表示在在名单里面
next()
} else {
next('/login') // 跳到登录页
}
}
NProgress.done() // 手动强制关闭一次 为了解决 手动切换地址时 进度条的不关闭的问题
})
// 后置守卫
router.afterEach(function() {
NProgress.done() // 关闭进度条
})
注意:在导航守卫的位置,我们添加了NProgress的插件,可以完成进入时的进度条效果
.sidebar-container {
background: -webkit-linear-gradient(bottom, #3d6df8, #5b8cff);
}
.scrollbar-wrapper {
background: url('~@/assets/common/leftnavBg.png') no-repeat 0 100%;
}
注意:在scss中,如果我们想要使用@别名,需要在前面加上一个~才可以
.el-menu {
border: none;
height: 100%;
width: 100% !important;
a{
li{
.svg-icon{
color: #fff;
font-size: 18px;
vertical-align: middle;
.icon{
color:#fff;
}
}
span{
color: #fff;
}
&:hover{
.svg-icon{
color: #43a7fe
}
span{
color: #43a7fe;
}
}
}
}
}
需要先在src/setttings.js中配置:
module.exports = {
title: '人力资源管理平台',
/**
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: true // 显示logo
}
然后在src/layout/components/Sidebar/Logo.vue中设置Logo图片路径:
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link key="collapse" class="sidebar-logo-link" to="/">
<img src="@/assets/common/logo.png" class="sidebar-logo ">
</router-link>
</transition>
</div>
最后配置Logo图片样式:
// 小图样式 当导航栏缩小时显示的logo样式
&.collapse {
.sidebar-logo {
margin-right: 0px;
width: 32px;
height: 32px;
}
}
// 大图样式 当导航栏展开时显示的logo样式
.sidebar-logo {
width: 140px;
vertical-align: middle;
margin-right: 12px;
}
<div class="app-breadcrumb">
XXX股份有限公司
<span class="breadBtn">体验版</span>
</div>
.app-breadcrumb {
display: inline-block;
font-size: 18px;
line-height: 50px;
margin-left: 10px;
color: #ffffff;
cursor: text;
.breadBtn {
background: #84a9fe;
font-size: 14px;
padding: 0 10px;
display: inline-block;
height: 30px;
line-height: 30px;
border-radius: 10px;
margin-left: 15px;
}
}
.navbar {
background-image: -webkit-linear-gradient(left, #3d6df8, #5b8cff);
}
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img src="@/assets/common/bigUserHeader.png" class="user-avatar">
<span class="name">管理员</span>
<i class="el-icon-caret-bottom" style="color:#fff" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<router-link to="/">
<el-dropdown-item>
首页
</el-dropdown-item>
</router-link>
<a target="_blank" href="https://gitee.com/shuiruohanyu/hrsaas53">
<el-dropdown-item>项目地址</el-dropdown-item>
</a>
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
.user-avatar {
cursor: pointer;
width: 30px;
height: 30px;
border-radius: 15px;
vertical-align: middle;
}
.name {
color: #fff;
vertical-align: middle;
margin-left:5px;
}
.user-dropdown {
color: #fff;
}
/**
* 获取用户的基本资料
*
* **/
export function getUserInfo() {
return request({
url: '/sys/profile',
method: 'post'
})
}
在src/utils/request.js文件中统一注入token:
service.interceptors.request.use(config => {
// 在这个位置需要统一的去注入token
if (store.getters.token) {
// 如果token存在 注入token
config.headers['Authorization'] = `Bearer ${store.getters.token}`
}
return config // 必须返回配置
}, error => {
return Promise.reject(error)
})
在src/store/modules/user.js文件中添加如下代码:
import { getUserInfo } from '@/api/user'
const state = {
userInfo: {} // 定义一个空的对象
}
const mutations = {
// 设置用户信息
setUserInfo(state, userInfo) {
state.userInfo = { ...userInfo } // 用 浅拷贝的方式去赋值对象 因为这样数据更新之后,才会触发组件的更新
},
}
const action = {
// 获取用户资料action
async getUserInfo (context) {
const result = await getUserInfo() // 获取返回值
context.commit('setUserInfo', result) // 将整个的个人信息设置到用户的vuex数据中
return result
}
}
在src/store/getters.js文件中建立用户名的映射:
const getters = {
name: state => state.user.userInfo.username // 建立用户名称的映射
}
export default getters
用户资料要求必须有token才可以获取,那么我们就可以在确定有token的位置去获取用户资料
在src/permission.js文件中添加如下代码:
// 如果没有用户id说明还没获取到用户资料,才会调用vuex的获取资料的action
if(!store.state.user.userInfo.userId) {
await store.dispatch('user/getUserInfo')
}
在layout/components/Navbar.vue文件中添加如下代码:
...mapGetters([
'name',
'staffPhoto'
])
<img :src="staffPhoto" class="user-avatar">
<span class="name">{{ name }}</span>
在src/store/modules/user.js文件中添加如下代码:
const mutations = {
removeToken(state) {
state.token = null // 将vuex的数据置空
removeToken() // 同步到缓存
}
removeUseInfo(state) {
state.userInfo = {}
}
}
const action = {
// 登出的action
logout(context) {
// 删除token
context.commit('removeToken') // 不仅仅删除了vuex中的 还删除了缓存中的
// 删除用户资料
context.commit('removeUserInfo') // 删除用户信息
}
}
在src/layout/components/Navbar.vue中添加如下代码:
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">退出登录</span>
</el-dropdown-item>
async logout() {
await this.$store.dispatch('user/logout') // 这里不论写不写 await 登出方法都是同步的
this.$router.push(`/login`) // 跳到登录
}
在src/utils/auth.js文件中添加如下代码:
const timeKey = 'hrsaas-timestamp-key' // 设置一个独一无二的key
// 获取时间戳
export function getTimeStamp() {
return Cookies.get(timeKey)
}
// 设置时间戳
export function setTimeStamp() {
Cookies.set(timeKey, Date.now())
}
在src/utils/request.js文件中添加如下代码:
import axios from 'axios'
import store from '@/store'
import router from '@/router'
import { Message } from 'element-ui'
import { getTimeStamp } from '@/utils/auth'
const TimeOut = 3600 // 定义超时时间
// 请求拦截器
service.interceptors.request.use(config => {
// config 是请求的配置信息
// 注入token
if (store.getters.token) {
// 只有在有token的情况下 才有必要去检查时间戳是否超时
if (IsCheckTimeOut()) {
// 如果它为true表示 过期了
// token没用了 因为超时了
store.dispatch('user/logout') // 登出操作
// 跳转到登录页
router.push('/login')
return Promise.reject(new Error('token超时了'))
}
config.headers['Authorization'] = `Bearer ${store.getters.token}`
}
return config // 必须要返回的
}, error => {
return Promise.reject(error)
})
// 是否超时
// 超时逻辑 (当前时间 - 缓存中的时间) 是否大于 时间差
function IsCheckTimeOut() {
var currentTime = Date.now() // 当前时间戳
var timeStamp = getTimeStamp() // 缓存时间戳
return (currentTime - timeStamp) / 1000 > TimeOut
}
export default service
在src/modules/user.js中添加如下代码:
import { getToken, setToken, removeToken, setTimeStamp } from '@/utils/auth'
async login(context, data) {
const result = await login(data)
context.commit('setToken', result)
// 写入时间戳
setTimeStamp() // 将当前的最新时间写入缓存
}
被动接收的意思是当我们发送请求给后端时,后端告诉我们token超时了,我们被迫做出处理
在src/utils/request.js中添加如下代码:
// 响应拦截器
service.interceptors.response.use(response => {
// axios默认加了一层data
const { success, message, data } = response.data
// 要根据success的成功与否决定下面的操作
if (success) {
return data
} else {
// 业务已经错误了 还能进then ? 不能 ! 应该进catch
Message.error(message) // 提示错误消息
return Promise.reject(new Error(message))
}
}, error => {
// error 信息 里面 response的对象
if (error.response && error.response.data && error.response.data.code === 10002) {
// 当等于10002的时候 表示 后端告诉我token超时了
store.dispatch('user/logout') // 登出action 删除token
router.push('/login')
} else {
Message.error(error.message) // 提示错误信息
}
return Promise.reject(error)
})
今天主要实现了左侧和顶部导航栏模块和的布局,以及补全了登录认证流程,添加了退出登录功能,代码较多,请细细品味