本系列将从零开始创建一个项目,文章将持续更新
项目代码: https://github.com/no-pear/edu-fed.git
登录和认证
一、登录
1)页面基本布局
2)登录接口封装
import request from '@/utils/request'
import qs from 'qs'
// import store from '@/store'
interface User {
phone: string;
password: string;
}
// 登录
export const login = (form: User) => {
return request({
method: 'POST',
url: '/front/user/login',
// headers: { 'content-type': 'application/x-www-form-urlencoded' },
/**
* 如果 data 是普通对象, 则 Content-Type 是 application/json
* 如果 data 是 qs.stringfity(data) 转换之后的数据:key=value&key=value, 则 Content-Type 会被设置成 application/x-www-form-urlencoded
* 如果 data 是 FormData 对象,则 Content-Type 是 multipart/form-data
*/
data: qs.stringify(form) // axios 默认发送的是 application/json 格式的数据
})
}
3)登录处理
- 表单验证
- 验证通过提交表单
- 处理请求结果
- 成功:跳转到首页
- 失败:给出提示
src/login/index.vue
<template>
<div class="login">
<!-- :model="ruleForm" :rules="rules" ref="ruleForm" -->
<el-form ref="form" :model="form" :rules="rules" label-width="80px" label-position='top'>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password"></el-input>
</el-form-item>
<el-form-item>
<el-button :loading='isLoginLoading' type="primary" @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
// import request from '@/utils/request'
// import qs from 'qs'
import { Form } from 'element-ui'
import { login } from '@/services/user'
export default Vue.extend({
name: 'LoginIndex',
data () {
return {
form: {
phone: '15510792995',
password: '111111'
},
isLoginLoading: false,
rules: {
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 18, message: '长度在 6 到 18 个字符', trigger: 'blur' }
]
}
}
},
methods: {
async onSubmit () {
try {
// 1. 表单验证
await (this.$refs.form as Form).validate()
// 防止登录按钮多次点击
this.isLoginLoading = true
// 2. 验证通过 - 提交表单
// const { data } = await request({
// method: 'POST',
// url: '/front/user/login',
// headers: { 'content-type': 'application/x-www-form-urlencoded' },
// data: qs.stringify(this.form)
// })
const { data } = await login(this.form)
// console.log(data)
// 3. 处理请求结果
// 成功:跳转到首页
// 失败:给出提示
if (data.state !== 1) {
this.$message.error(data.message)
} else {
// 存储登录用户信息
this.$store.commit('setUser', data.content)
// 跳转首页
// this.$router.push({
// name: 'home'
// })
// 登录成功跳转到首页或者跳转到登录前想去到的页面
this.$router.push(this.$route.query.redirect as string || '/')
this.$message.success('登录成功')
}
this.isLoginLoading = false
} catch (error) {
console.log('格式校验失败', false)
}
}
}
})
</script>
<style lang="scss" scoped>
.login {
background-color: $body-bg;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
.el-form {
background: #fff;
padding: 20px;
border-radius: 5px;
width: 300px;
.el-button {
width: 100%;
}
}
}
</style>
二、认证
对于某些页面,必须得登录才能访问,所以登录的时候服务器会返回一个 access_token,以后每次访问都可以在请求头中添加上,access_token 有
有效期,过了有效期后,通过 refresh_token 去再次获取 access_token
1)路由
/**
* 全局前置守卫
* to: Route: 即将要进入的目标 路由对象
* from: Route: 当前导航正要离开的路由
* next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
*/
router.beforeEach((to, from, next) => {
// console.log('to--->', to)
// console.log('from--->', from)
// to.matched 是一个数组(匹配到的路由信息)
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.state.user) {
next({
path: '/login',
query: { // 通过 url 传递查询字符串参数
redirect: to.fullPath // 把登录成功需要返回的页面告诉登录页面
}
})
} else {
next()
}
} else {
next()
}
})
2)请求拦截器
request.interceptors.request.use(function (config) {
// Do something before request is sent
// console.log(config)
const { user } = store.state
if (user && user.access_token) {
config.headers.Authorization = user.access_token
}
return config
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
3)响应拦截器
// 响应拦截器
let isRefreshing = false // 控制刷新 token 的状态
let requests: any[] = [] // 存储刷新 token 期间过来的 401 请求
request.interceptors.response.use(function (response) { // 2xx 状态码
// console.log('response--->', response)
return response
}, async function (error) { // 超出 2xx 范围状态码
// console.log('error--->', error)
if (error.response) { // 请求发出去收到响应了,但是状态码超出了 2xx 范围
const { status } = error.response
if (status === 400) {
Message.error('请求参数错误')
} else if (status === 401) {
/**
* token 失效
* 如果有 refresh_token 则尝试使用 refresh_token 获取新的token
* 如果没有,则直接跳转登录页
*/
if (!store.state.user) {
redirectLogin()
return Promise.reject(error)
}
// 刷新 token
if (!isRefreshing) {
isRefreshing = true // 开启刷新状态
return refreshToken().then(res => {
if (!res.data.success) {
throw new Error('刷新 token 失败')
}
// 刷新 token 成功
store.commit('setUser', res.data.content)
// 把 requests 队列中的请求发送出去
requests.forEach(cb => {
cb()
})
// 重置 requests
requests = []
// console.log('error config-->', error.config) // 失败请求的配置信息
return request(error.config) // 第一个请求重新发送
}).catch(err => {
console.log('err-->', err)
// 清除当前登录用户状态
store.commit('setUser', null)
// 回到登录页
redirectLogin()
return Promise.reject(error)
}).finally(() => {
isRefreshing = false // 重置刷新状态
})
return
}
// 刷新状态下,把请求挂起放到 requests 数组中
return new Promise(resolve => {
requests.push(() => {
resolve(request(error.config))
})
})
// try {
// // 尝试获取新的 token
// const { data } = await axios.create()({
// method: 'POST',
// url: '/front/user/refresh_token',
// data: qs.stringify({
// refreshtoken: store.state.user.refresh_token
// })
// })
// console.log('data:', data)
// /**
// * 把刷新拿到的新的 refresh_token 更新到容器和本地存储中
// * 获取新的 token 成功, 把失败的请求重新发送出去
// */
// store.commit('setUser', data.content)
// console.log('error config-->', error.config) // 失败请求的配置信息
// return request(error.config)
// } catch (error) {
// // 清除当前登录用户状态
// store.commit('setUser', null)
// // 回到登录页
// redirectLogin()
// return Promise.reject(error)
// }
} else if (status === 403) {
Message.error('没有权限,请联系管理员')
} else if (status === 404) {
Message.error('请求资源不存在')
} else if (status >= 500) {
Message.error('服务器错误')
}
} else if (error.request) { // 请求发出去没收到响应
Message.error('请求响应失败')
} else { // 在设置请求是发生了一些事情,触发了一个错误
Message.error(`请求失败: ${error.message}`)
}
return Promise.reject(error)
})
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!