登录模块
设计UI结构,修改样式,UI结构中重点是登录表单验证。手机号和密码验证
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
auto-complete="on"
label-position="left"
>
<div class="title-container">
<h3 class="title">
<img
src="@/assets/common/login-logo.png"
alt=""
>
</h3>
</div>
<el-form-item prop="mobile">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
v-model="loginForm.mobile"
placeholder="请输入手机号"
name="mobile"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="请输入密码"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
@keyup.enter="submit"
/>
<span
class="show-pwd"
@click="showPwd"
>
<svg-icon
:icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
/>
</span>
</el-form-item>
<el-button
class="loginBtn"
:loading="loading"
type="primary"
style="width:100%;margin-bottom:30px;"
@click.native.prevent="handleLogin"
@keyup.enter="submit"
>登录</el-button>
<div class="tips">
<span style="margin-right:20px;">账号:13800000002</span>
<span> 密码: 123456</span>
</div>
</el-form>
data () {
const validateMobile = (rule, value, callback) => {
validMobile(value) ? callback() : callback(new Error('手机号格式不正确'))
}
return {
loginForm: {
mobile: '13800000002',
password: '123456'
},
loginRules: {
mobile: [
{ required: true, trigger: 'blur', message: '手机号不能为空' },
{ validator: validateMobile, trigger: 'blur' }
],
password: [
{ required: true, trigger: 'blur', message: '密码不能为空' },
{ min: 6, max: 16, message: '密码的长度在6-16位之间', trigger: 'blur' }
]
},
loading: false,
passwordType: 'password',
redirect: undefined
}
},
methods: {
handleLogin () {
// 手动校验
this.$refs.loginForm.validate(async isOK => {
if (isOK) {
try {
this.loading = true
await this['user/login'](this.loginForm)
this.$router.push('/')
} catch (error) {
console.log(error)
} finally {
this.loading = false
}
}
})
}
}
封装登录的接口
使用Vuex来管理状态以及存储数据
封装一个工具用来存储token到cookies或localstorage
import Cookies from 'js-cookie'
const TokenKey = '###-###-key'
const timeKey = '###-###-key'
// 获取token
export function getToken () {
return Cookies.get(TokenKey)
}
// 获取时间戳
export function getTimeStamp () {
return Cookies.get(timeKey)
}
// 设置token
export function setToken (token) {
return Cookies.set(TokenKey, token)
}
// 设置时间戳
export function setTimeStamp () {
return Cookies.set(timeKey, Date.now())
}
// 删除token
export function removeToken () {
return Cookies.remove(TokenKey)
}
当token发生变化时就在store里管理
// 状态
// vuex的持久化 在utils工具中设定存储 可以用cookie、loadStorage等管理token,然后在vuex(store管理vuex)用时调出来用 可以进行状态设定存储 修改等操作(修改时有同步和异步) 每次修改后同步给缓存(也就是每次都存一下setToken)
const state = {
// 设置token共享状态(从本地获取)
token: getToken(),
userInfo: {} // 放一个空的对象,后面存放数据给别人用
}
// 修改状态
const mutations = {
// 设置和删除token
setToken (state, token) {
state.token = token // (每次操作后修改状态并存入)
// 同步给缓存
setToken(token) // vuex和 缓存数据的同步
},
removeToken (state) {
state.token = null // 删除vuex的token
removeToken() // 先清除 vuex 再清除缓存 vuex和 缓存数据的同步
},
// 设置和删除用户资料
setUserInfo (state, result) { // result为获取到的用户资料结果
state.userInfo = result
},
removeUserInfo (state) {
state.userInfo()
}
}
// 执行异步
// 获取登录信息也好,获取用户基本资料也好,只要是异步操作都在这里执行
const actions = { }
执行异步操作,调取接口,获取数据并存储Vuex中
第一次登录时,设置基础地址。
服务器响应拦截器工作,返回两个结果,响应和失败,响应了如果成功返回数据如果人为失败则new一个错误对象提示,失败则return失败的信息
// 响应拦截器
// 两个函数一个成功一个失败:请求成功则解构数据判断成功了拿数据失败了提示错误失败,或者一开始请求时就会失败
service.interceptors.response.use(response => {
const { success, message, data } = response.data
if (success) {
return data
} else {
// 业务错误 要进入catch,所以return
Message.error(message)
// 人为错误,故new一个错误对象
return Promise.reject(new Error(message))
}
}, error => {
//此处为token失效的被动处理,可忽略
if (error.response && error.response.data && error.response.data.code === 10002) {
store.dispatch('user/logout') // 执行登出操作 删除token数据
router.push('/login') // 路由跳转登录页面
//此处为token失效的被动处理,可忽略
} else {
Message.error(error.message)
}
return Promise.reject(error) // 返回错误,直接进入catch
})
此时当已经成功获取数据,并且在Vuex中存储。然后在页面中调取方法获取数据渲染。...mapActions([''])方式
此时完成登录页的数据渲染及跳转登录
主页
主页跳转时设个权限拦截(路由守卫)判断是否有token
const whiteList = ['/login', '/404'] // 定义白名单 所有不受权限控制的页面
// 路由前置守卫
router.beforeEach(async function (to, from, next) {
NProgress.start() // 开启进度条
// 权限拦截首先判断是否有token,是否是登陆页面,符合就跳转主页。或者是否在白名单也可直接跳转。否则跳回登录页
// 先判断是否有token
if (store.getters.token) {
// 如果有token继续判断是不是去登录页 是跳转主页 否放行等待处理
if (to.path === '/login') {
// 表示取得是登录页
next('/')
} else {
// 这是第二步,有token却不是登录页需要放行,此时也需要用户信息了
if (!store.getters.userId) {
await store.dispatch('user/getUserInfo') // 获取用户信息的
}
next() // 直接放行
}
} else {
// 如果未携带token
if (whiteList.indexOf(to.path) > -1) {
next()
} else {
// 跳回到登录页
next('/login')
}
}
// 手动关闭进度条
NProgress.done()
})
// 后置守卫
router.afterEach(function () {
NProgress.done()
})
主页页面布局部分(省略)
封装获取用户信息的接口
每次请求需携带token,故在请求拦截器处统一注入
我们仍然是异步获取数据Actions,然后将数据暂存Vuex
因为获取用户信息需要有token,所以可以在有token的地方设置路由权限(路由前置守卫在有token的情况下是否时登录页是的话跳转,不是的话路由守卫一下判断是否有用户信息,没有要及时await获取)获取信息
将获取到的用户信息数据渲染到页面中
实现登出,完成闭环
登出时仍可异步操作数据,删除token和用户信息,路由跳转页面
其中有一个问题即token失效(主动接入/被动处理)
设置一个时间戳
在登录时设置时间戳
在请求拦截器中有token的情况下检查时间戳是否超时
// 请求拦截器
service.interceptors.request.use(config => {
// 需要在每次访问时携带一个token,故而在这个位置统一去注入,省去很多麻烦
if (store.getters.token) {
// 只有在有token的情况下 才有必要去检查时间戳是否超时
if (IsCheckTimeOut()) { // 表示过期 跳回到登录页
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 () {
const currentTime = Date.now() // 当前时间戳
const timeStamp = getTimeStamp() // 缓存时间戳
return (currentTime - timeStamp) / 1000 > TimeOut // 表示超时
}
时间戳可存入cookies,用时取,每次登录时存入一个时间戳
好了以上完结一个项目的登录功能。
总结一下:
PC项目登录登出功能全过程-登录页/主页
登录页:
UI静态页面设计、
css样式修改、
封装接口、
调取接口(异步执行action)、
响应拦截器拦截判断、
拿到数据暂存Vuex 在页面中调取Vuex中的数据渲染页面
主页:
跳转登录到主页后会加载数据,以及是否有token可以进行跳转,故可以设置权限拦截许可(路由守卫)前置守卫是否携带token。
成功跳转后需:
封装获取用户信息的接口、
铺设主页的静态页面及样式修改、
调取接口获取数据(action)异步执行(Vuex的作用1可以管理状态2则可以存储数据)、
发送请求时请求拦截器可以统一注入token以及携带请求头,因大多数请求需要token、封装一个存储数据的文件cookies存储,用户登录登出可存 取 删除数据。、
最后仍将暂存Vuex中的数据渲染到主页页面中、
权限拦截切记(路由守卫)一般放行后都应该有数据此时路由守卫在有token的时候就守卫判断一下。
最后就是token过期的主动介入和被动处理:
定义一个判断超时的逻辑,主动介入即在登陆时设置一个时间戳和获取当前时间戳,在请求拦截器中计算判断、被动处理即在响应拦截器判断token状态,过期后自动退出登录。