目标
能实现登录页面的布局
能实现基本登录功能
能掌握 Vant 中 Toast 提示组件的使用
能理解 API 请求模块的封装
能理解发送验证码的实现思路
能理解 Vant Form 实现表单验证的使用
这里主要使用到三个 Vant 组件:
NavBar 导航栏
Form 表单
Field 输入框
Button 按钮
写样式的原则:将公共样式写到全局(src/styles/index.less
),将局部样式写到组件内部。对于需要更改样式,自己添加 class 类名。
官方文档实现:
实现效果:
最左侧显示的图标无法满足需求的情况下,可以通过 slot 实现,插入自定义的图标。可以插入的slot如下所示:
具体实现,通过插入 i 标签,设置 iconfont 类名。
官方文档:
实现效果:
但是在表单中,除了提交按钮外,可能还有一些其他的功能性按钮,如发送验证码按钮。在使用这些按钮时,要注意将 native-type设置为button,否则会触发表单提交。
功能需求:
注册点击登录的事件
获取表单数据(根据接口要求使用 v-model 绑定)
表单验证
发请求提交
根据请求结果做下一步处理
点击发送验证码功能
功能需求:
注意:
Token 是用户登录成功之后服务端返回的一个身份令牌,在项目中的多个业务中需要使用到:
访问需要授权的 API 接口
校验页面的访问权限
...
但是我们只有在第一次用户登录成功之后才能拿到 Token。
所以为了能在其它模块中获取到 Token 数据,我们需要把它存储到一个公共的位置,方便随时取用。
往哪儿存?
本地存储
获取麻烦
数据不是响应式
Vuex 容器(推荐)
获取方便
响应式的
登录成功,将 Token 存储到 Vuex 容器中
获取方便
响应式
为了持久化,还需要把 Token 放到本地存储
持久化
登录成功以后将后端返回的 token 相关数据存储到容器中
创建 src/utils/storage.js
模块
登录成功之后后端会返回两个 Token:
token
:访问令牌,有效期2小时
refresh_token
:刷新令牌,有效期14天,用于访问令牌过期之后重新获取新的访问令牌
我们的项目接口中设定的 Token
有效期是 2 小时
,超过有效期服务端会返回 401
表示 Token 无效或过期了。
为什么过期时间这么短?
为了安全,例如 Token 被别人盗用
过期了怎么办?
让用户重新登录,用户体验太差了
使用 refresh_token
解决 token
过期
后续拓展Token 过期处理 ,在学习测试的时候如果收到 401 响应码,请重新登录再测试。
概述:服务器生成token的过程中,会有两个时间,一个是token失效时间,一个是token刷新时间,刷新时间肯定比失效时间长,当用户的 token
过期时,你可以拿着过期的token去换取新的token,来保持用户的登陆状态,当然你这个过期token的过期时间必须在刷新时间之内,如果超出了刷新时间,那么返回的依旧是 401。
处理流程:
在axios的拦截器中加入token刷新逻辑
当用户token过期时,去向服务器请求新的 token
把旧的token替换为新的token
然后继续用户当前的请求
在请求的响应拦截器中统一处理 token 过期(后续拓展)。
/** * 封装 axios 请求模块 */ import axios from "axios"; import jsonBig from "json-bigint"; import store from "@/store"; import router from "@/router"; // axios.create 方法:复制一个 axios const request = axios.create({ baseURL: "http://ttapi.research.itcast.cn/" // 基础路径 }); /** * 配置处理后端返回数据中超出 js 安全整数范围问题 */ request.defaults.transformResponse = [ function(data) { try { return jsonBig.parse(data); } catch (err) { return {}; } } ]; // 请求拦截器 request.interceptors.request.use( function(config) { const user = store.state.user; if (user) { config.headers.Authorization = `Bearer ${user.token}`; } // Do something before request is sent return config; }, function(error) { // Do something with request error return Promise.reject(error); } ); // 响应拦截器 request.interceptors.response.use( // 响应成功进入第1个函数 // 该函数的参数是响应对象 function(response) { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data return response; }, // 响应失败进入第2个函数,该函数的参数是错误对象 async function(error) { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error // 如果响应码是 401 ,则请求获取新的 token // 响应拦截器中的 error 就是那个响应的错误对象 console.dir(error); if (error.response && error.response.status === 401) { // 校验是否有 refresh_token const user = store.state.user; if (!user || !user.refresh_token) { router.push("/login"); // 代码不要往后执行了 return; } // 如果有refresh_token,则请求获取新的 token try { const res = await axios({ method: "PUT", url: "http://ttapi.research.itcast.cn/app/v1_0/authorizations", headers: { Authorization: `Bearer ${user.refresh_token}` } }); // 如果获取成功,则把新的 token 更新到容器中 console.log("刷新 token 成功", res); store.commit("setUser", { token: res.data.data.token, // 最新获取的可用 token refresh_token: user.refresh_token // 还是原来的 refresh_token }); // 把之前失败的用户请求继续发出去 // config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有 // return 把 request 的请求结果继续返回给发请求的具体位置 return request(error.config); } catch (err) { // 如果获取失败,直接跳转 登录页 console.log("请求刷新 token 失败", err); router.push("/login"); } } return Promise.reject(error); } ); export default request;
下一篇:车商记app