目录
目录
登录注册
准备
API文档
创建组件并配置路由
实现基本登录功能
登录状态提示
表单验证
验证码处理
发送验证码前先)验证手机号
使用倒计时组件
添加发送按钮的loading
存储用户Token
优化封装本地存储操作模块
JSON和JS对象对比:
JSON和JS对象互转
关于Token过期问题(后期讲解)
登录注册
目标:
- 能实现登录页面的布局
- 能实现基本登录功能
- 能掌握vant中Toast提示组件的使用
- 能理解API请求模块的封装
- 能理解发送验证码的实现思路
- 能了解在vue中处理表单验证的方式
准备
API文档
创建组件并配置路由
页面结构
<template>
<div class="login-container">
登录页面
</div>
</template>
<script>
export default {
name: "LoginIndex",
data() {
return {};
}
};
</script>
<style></style>
路由配置
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
// 路由表
const routes = [
{
path: '/login',
name: '/login',
// 路由懒加载形式
// 完整写法:
// component: () => import('@/views/login/index.vue')
component: () => import('@/views/login')
}
]
export default new Router({
routes
})
实现基本登录功能
接口配置
1.接口:http://ttapi.research.itcast.cn/app/v1_0/authorizations
2.POST方式
3.接口需要传的参数:
{
"mobile": "13911111111",
"code": "246810"
}
登录联调接口
1.配置登录模块接口 api/user.js
2. 登录组件请求接口
- 引入模块 import { login } from "@/api/user";
- 点击登录提交方法 await login(user);
async onSubmit() {
// 1. 获取表单数据
const user = this.user;
// 2.表单验证
this.$toast.loading({
message: "登录中..",
forbidClick: true, // 禁用背景点击
duration: 2000 // 持续时间,默认2000,若为0则持续展示
});
// 3. 提交表单请求登录
try {
const res = await login(user);
// console.log("登录成功", res);
this.$toast.success("登录成功");
} catch (err) {
if (err.response.status === 400) {
console.log("手机号或验证码错误");
this.$toast.fail("手机号或验证码错误");
} else {
console.log("登录失败,请稍后重试", err);
this.$toast.fail("登录失败,请稍后重试");
}
}
// 4.根据请求响应结果处理后续操作
}
登录状态提示
1.加载:
this.$toast.loading({
message: "登录中..",
forbidClick: true, // 禁用背景点击
duration: 2000 // 持续时间,默认2000,若为0则持续展示
});
2.成功:this.$toast.success("登录成功"); 失败:this.$toast.fail("手机号或验证码错误");
表单验证
基于vant的表单验证:
1.使用van-form组件包裹所有的表单项 van-field,包括登录按钮都包含在van-form组件
<van-form @submit="onLogin"></van-form>
2.给van-form绑定onLogin事件,当表单提交时触发 onLogin
(只有表单验证通过才会调用onLogin事件)
注:在此之前onLogin事件的名字为onSubmit,这里方便识别改成onLogin
3.使用Field的的rules属性定义校验规则
法一(用于验证规则较少)
:rules="[{ required: true, message: '请输入手机号' }]"
法二(用于验证规则较多)
首先 :rules="formRules.mobile"
然后 formRules: {
mobile: [
{ required: true, message: "请输入正确的手机号" },
{ pattern: /^1[3|5|7|8]\d{9}$/, message: "手机号格式错误" }
],
code: [
{ required: "true", message: "请输入验证码" },
{ pattern: /^\d{6}$/, message: "验证码格式错误" }
]
}
自定义错误消息提示
1.van-form涉及的三个属性show-error,show-error-message,validate-first
2.绑定表单验证失败触发 @failed="onFailed"
3.onFailed方法
onFailed(error) {
if (error.errors[0]) {
this.$toast({
message: error.errors[0].message,
position: "top"
});
}
}
验证码处理
(发送验证码前先)验证手机号
1.基本流程
// 1.发送验证码前需要先检验手机号
// 验证通过后->请求发送验证码->用户接收短信->输入验证码->请求登录
// 2.请求发送验证码->隐藏发送按钮,显示倒计时
// 3.倒计时结束->隐藏倒计时,显示发送按钮
2.使用van-form的validate属性,需要先ref绑定实例,因为validate的返回值是一个Promise,所以要用到.then()方法去接收:
this.$refs['login-form'].validate('mobile').then(data => {
console.log(data)
})
这里我们为了方便起见,直接使用async await 方法
try{
await this.$refs['login-form'].validate('mobile')
// 验证通过,请求发送验证码
}
// 验证失败
catch(err){
this.$toast({
message: err.message,
position: 'top'
})
}
接口配置
1.接口:http://ttapi.research.itcast.cn/mp/v1_0/sms/codes/:mobile
2.GET方式
3.接口需要传的参数:
{
"mobile": "13911111111",
"code": "246810"
}
验证码联调接口
1.配置api/user.js 验证码
// 发送短信验证码
export const sendSms = mobile => { // 参数mobile
return request({
method: 'GET',
url: `/app/v1_0/sms/codes/${mobile}`
})
}
2.该组件页请求接口
- 引入模块 import { login, sendSms } from "@/api/user";
- 点击发送验证码的方法 await sendSms(this.user.mobile);
// 发送验证码
async onSendSms() {
// 1.检验手机号
// 验证通过->请求发送验证码->用户接收短信->输入验证码->请求登录
// 2.请求发送验证码->隐藏发送按钮,显示倒计时
// 3.倒计时结束->隐藏倒计时,显示发送按钮
// this.$refs['login-form'].validate('mobile').then(data => {
// console.log(data)
// })
try {
await this.$refs["login-form"].validate("mobile");
// 验证通过,请求发送验证码
await sendSms(this.user.mobile);
} catch (err) {
// try 里面任何代码的错误都会进入catch
let message = "";
if (err && err.response && err.response.status === 429) {
// 发送短信失败的错误提示
message = "发送太频繁了,请稍后重试";
} else if (err.name === "mobile") {
// 发送验证失败的错误提示
message = err.message;
} else {
// 未知错误
message = "发送失败,请稍后重试";
}
this.$toast({
message: message,
position: "top"
});
}
}
使用倒计时组件
1.使用countDown倒计时组件
// 倒计时显示v-if
<van-count-down
v-if="isCounDownShow"
:time="1000 * 60"
format="ss s"
/>
// 否则发送验证码按钮显示 v-else
2.初始化isCounDownShow: false,用来控制倒计时和发送按钮的显示状态
3.添加倒计时结束的方法,隐藏倒计时 @finish="isCounDownShow = false"
添加发送按钮的loading
用户点击完”发送验证码“进行网络请求时,页面并没有立即显示”倒计时“,这会导致用户以为并未点击上”发送验证码“按钮,通过控制台可以看出实际上已经点击了,防止网络过慢,用户多次触发发送行为
解决:
1.点击完后可以将按钮设置为禁用
2.点击完后可以将按钮设置为loading属性,表示正在加载
存储用户Token
用户只有在第一次登录成功之后才能拿到Token,为了后续在其它模块中能获取到Token数据,需要存储到一个公共位置
- 本地存储,不推荐,获取麻烦,数据不是响应式
- Vuex容器,推荐,获取方便,响应式
1.创建src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// user: null,
// 还原为对象
user: JSON.parse(window.localStorage.getItem('user')) // 当前登录用户的登录状态(token等数据)
},
// 修改state对象的值
mutations: {
// 参数:state对象,data所传入的参数
setUser (state, data) {
state.user = data
// 容器中的数据不是持久化的,刷新后就不存在了
// 为了防止页面刷新数据丢失,还需要把数据存储到本地存储中,这里仅仅是为了持久化数据
// 对象state.user不能直接存入本地存储中,要转换为字符串
window.localStorage.setItem('user', JSON.stringify(state.user))
}
}
})
重点:
容器中的数据不是持久化的,刷新后就不存在了
为了防止页面刷新数据丢失,还需要把数据存储到本地存储中,这里仅仅是为了持久化数据
对象state.user不能直接存入本地存储中,要转换为JSON字符串 JSON.stringify()
2.在登录组件login/index.vue发起网络请求时并登录成功,将后端返回的用户登录状态存入Vuex容器中
// 将后端返回的用户登录状态(token等数据)放到Vuex容器中
this.$store.commit("setUser", res.data.data);
优化封装本地存储操作模块
1.创建src/utils/storage.js文件
// 本地存储封装模块
export const getItem = name => {
const data = window.localStorage.getItem(name)
// 为什么把JSON.parse放到try-catch中
// 因为data可能不是JSON格式字符串
try {
// 尝试把data转为JavaScript对象
return JSON.parse(data)
} catch (err) {
// data不是JSON格式字符串,直接原样返回
return data
}
}
export const setItem = (name, value) => {
// 如果value是对象,就把value转为JSON格式字符串再存储
if (typeof value === 'object') {
value = JSON.stringify(value)
}
window.localStorage.setItem(value)
}
export const removeItem = name => {
window.localStorage.removeItem(name)
}
2.引入文件 import { getItem, setItem } from '@/utils/storage'
import Vue from 'vue'
import Vuex from 'vuex'
import { getItem, setItem } from '@/utils/storage'
Vue.use(Vuex)
const USER_KEY = 'user'
export default new Vuex.Store({
state: {
// user: null,
// 还原为对象
// user: JSON.parse(window.localStorage.getItem('user')) // 当前登录用户的登录状态(token等数据)
// 优化为:
user: getItem(USER_KEY)
},
// 修改state对象的值
mutations: {
// 参数:state对象,data所传入的参数
setUser (state, data) {
state.user = data
// 容器中的数据不是持久化的,刷新后就不存在了
// 为了防止页面刷新数据丢失,还需要把数据存储到本地存储中,这里仅仅是为了持久化数据
// 对象state.user不能直接存入本地存储中,要转换为JSON字符串
// window.localStorage.setItem('user', JSON.stringify(state.user))
// 优化为:
setItem(USER_KEY, state.user)
}
}
})
补充:
一直以为JSON是对象,然而网上查阅才发现JSON与JS有很大的不同。
var obj={width:100,height:200},这样的并不叫JSON,并且JSON只是一种数据格式,并不是具体的实例对象。但很多人把这样的JS对象当成JSON,JSON是不能用来存诸数据的
- var obj5={"width":100,"height":200,"name":"rose"}; /*我们可以把这个称做:JSON格式的JavaScript对象 */
- var str1='{"width":100,"height":200,"name":"rose"}';/*我们可以把这个称做:JSON格式的字符串 */
{"province": "Shanxi"}
可以理解为是一个包含province为Shanxi的对象,["Shanxi","Shandong"]
这是一个包含两个元素的数组
JSON和JS对象对比:
JSON和JS对象互转
关于Token过期问题(后期讲解)
登录成功之后后端会返回两个token:
- token:访问令牌,有效期2小时
- refresh_token:刷新令牌,有效期14天,用于访问令牌过期之后重新获取新的访问令牌
该项目接口中设定的token有效期是2小时,超过有效期服务端会返回401表示token无效或过期了
过期时间短是为了安全,防止token被盗用
过期怎么办?
1.让用户重新登录,用户体验太差
2.使用refresh_token解决token过期