1、准备

#1.1 创建组件并配置路由

1、创建 src/views/login/index.vue 并写入以下内容

<template><div class="login-container">登录页面</div>
</template><script>
export default {name: 'LoginPage',components: {},props: {},data () {return {}},computed: {},watch: {},created () {},mounted () {},methods: {}
}
</script><style scoped lang="less"></style>

2、然后在 src/router/index.js 中配置登录页的路由表

{path: '/login',name: 'login',component: () => import('@/views/login')
}

最后,访问 /login 查看是否能访问到登录页面。

#1.2 布局结构

这里主要使用到三个 Vant 组件:

  • NavBar 导航栏(opens new window)
  • Form 表单(opens new window)
    • Field 输入框(opens new window)
    • Button 按钮(opens new window)

一个经验:使用组件库中的现有组件快速布局,再慢慢调整细节,效率更高(刚开始可能会感觉有点麻烦,越用越熟,慢慢的就有了自己的思想)。

src/views/login/index.vue

<template><div class="login-container"><!-- 导航栏 --><van-nav-bar class="page-nav-bar" title="登录" /><!-- /导航栏 --><!-- 登录表单 --><van-form @submit="onSubmit"><van-fieldname="用户名"placeholder="请输入手机号"/><van-fieldname="验证码"placeholder="请输入验证码"/><div class="login-btn-wrap"><!--其实van-button按钮如果包裹在van-form里面默认类型就是native-type,这里不加也可以--><van-button class="login-btn" block type="info" native-type="submit">登录</van-button></div></van-form><!-- /登录表单 --></div>
</template><script>
export default {name: 'LoginIndex',components: {},props: {},data () {return {}},computed: {},watch: {},created () {},mounted () {},methods: {onSubmit (values) {console.log('submit', values)}}
}
</script><style scoped lang="less"></style>

#1.3 布局样式

写样式的原则:将公共样式写到全局(src/styles/index.less),将局部样式写到组件内部。

1、src/styles/index.less

body {background-color: #f5f7f9;
}.page-nav-bar {background-color: #3296fa!important;.van-nav-bar__title {color: #fff;}.van-icon{color: #fff!important;}
}

2、src/views/login/index.vue

<template><div class="login-container"><!-- 导航栏 --><van-nav-bar class="page-nav-bar" title="登录" /><!-- /导航栏 --><!-- 登录表单 --><van-form @submit="onSubmit"><van-fieldname="用户名"placeholder="请输入手机号"><!--【增加图标】--><template #left-icon><i class="toutiao toutiao-shouji"></i></template></van-field><van-fieldname="验证码"placeholder="请输入验证码"><!--【增加图标】-->  <template #left-icon><i class="toutiao toutiao-yanzhengma"></i></template><!--【增加发送按钮】--><template #button><!--这里注意,要加上native-type,否则也可以实现表单提交--><van-button class="send-sms-btn" round size="small"  native-type="button" type="default">发送验证码</van-button></template></van-field><div class="login-btn-wrap"><van-button class="login-btn" block type="info" native-type="submit">登录</van-button></div></van-form><!-- /登录表单 --></div>
</template><script>
export default {name: 'LoginIndex',components: {},props: {},data () {return {}},computed: {},watch: {},created () {},mounted () {},methods: {onSubmit (values) {console.log('submit', values)}}
}
</script><style scoped lang="less">
.login-container {.toutiao {font-size: 37px;}.send-sms-btn {width: 152px;height: 46px;line-height: 46px;background-color: #ededed;font-size: 22px;color: #666;}.login-btn-wrap {padding: 53px 33px;.login-btn {background-color: #6db4fb;border: none;}}
}
</style>

#2、实现基本登录功能

思路:

  • 注册点击登录的事件
  • 获取表单数据(根据接口要求使用 v-model 绑定)
  • 表单验证
  • 发请求提交
  • 根据请求结果做下一步处理

#2.1 根据接口绑定获取表单数据

1、在登录页面组件的实例选项 data 中添加 user 数据字段

...
data () {return {user: {mobile: '',code: ''}}
}

2、在表单中使用 v-model 绑定对应数据

<!-- van-cell-group 仅仅是提供了一个上下外边框,能看到包裹的区域 -->
<van-cell-group><van-fieldv-model="user.mobile"name="mobile"requiredclearablelabel="手机号"placeholder="请输入手机号"/><van-fieldv-model="user.code"name="code"type="number"label="验证码"placeholder="请输入验证码"required/>
</van-cell-group>

2

最后测试。

一个小技巧:使用 VueDevtools 调试工具查看是否绑定成功。

#2.2 请求登录

1、创建 src/api/user.js 封装请求方法

/*** 用户相关的请求模块*/
import request from "@/utils/request"/*** 用户登录*/
export const login = data => {return request({method: 'POST',url: '/v1_0/authorizations',data})
}

2、导入方法,调用方法,发送请求

src/views/login/index.vue

import { login } from '@/api/user'

1

async onSubmit () {// 获取表单数据const user = this.user// 发送请求try {const res = await login(user)console.log('登录成功', res)} catch (err) {if (err.response.status === 400) {console.log('手机号或验证码错误')} else {console.log('登录失败,请稍后重试', err)}}
}

最后测试,查看network数据请求是否成功,控制台是否打印了数据。

#3、登录状态提示

Vant 中内置了Toast 轻提示 (opens new window)组件,可以实现移动端常见的提示效果。

// 简单文字提示
Toast("提示内容");// loading 转圈圈提示
Toast.loading({duration: 0, // 持续展示 toastmessage: "加载中...",forbidClick: true // 是否禁止背景点击
});// 成功提示
Ttoast.success("成功文案");// 失败提示
Toast.fail("失败文案");

提示:在组件中可以直接通过 this.$toast 调用。

另外需要注意的是:Toast 默认采用单例模式,即同一时间只会存在一个 Toast,如果需要在同一时间弹出多个 Toast,可以参考下面的示例

Toast.allowMultiple();const toast1 = Toast('第一个 Toast');
const toast2 = Toast.success('第二个 Toast');toast1.clear();
toast2.clear();

下面是为我们的登录功能增加 toast 交互提示。

src/views/login/index.vue

async onSubmit () {// 获取表单数据const user = this.user// 开始转圈圈 <===【增加等待框】this.$toast.loading({duration: 0, // 持续时间,0表示持续展示不停止forbidClick: true, // 是否禁止背景点击message: '登录中...' // 提示消息})try {const res = await login(user)console.log('登录成功', res)// 提示 success 或者 fail 的时候,会先把其它的 toast 先清除this.$toast.success('登录成功') // <===【增加成功框】} catch (err) {console.log('登录失败', err)if (err.response.status === 400) {this.$toast.fail('手机号或验证码错误')  // <===【失败框】} else {console.log('登录失败,请稍后重试', err)this.$toast.fail('登录失败,请稍后重试')  // <===【失败框】}}
}

假如请求非常快的话就看不到 loading 效果了,这里可以手动将调试工具中的网络设置为慢速网络。

测试结束,再把网络设置恢复为 Online 正常网络。

#4、表单验证

参考文档:Form 表单验证(opens new window)

element-ui的验证:https://element.eleme.cn/#/zh-CN/component/form

#4.1 增加验证规则对象

 data () {return {// 其他数据...// 验证规则对象userFormRules: {mobile: [{required: true,message: '手机号不能为空'}, {pattern: /^1[3|5|7|8]\d{9}$/,message: '手机号格式错误'}],code: [{required: true,message: '验证码不能为空'}, {pattern: /^\d{6}$/,message: '验证码格式错误'}]}}},

#4.2 绑定验证规则

 <van-fieldv-model="user.mobile"name="mobile"placeholder="请输入手机号":rules="userFormRules.mobile"   // <== 增加验证type="number"maxlength="11">
 <van-fieldv-model="user.code"name="code"placeholder="请输入验证码":rules="userFormRules.code"    // <== 增加验证type="number"maxlength="6">

完整代码

<template><div class="login-container"><!-- 导航栏 --><van-nav-bar class="page-nav-bar" title="登录" /><!-- /导航栏 --><!-- 登录表单 --><!--表单验证:1、给 van-field 组件配置 rules 验证规则参考文档:https://youzan.github.io/vant/#/zh-CN/form#rule-shu-ju-jie-gou2、当表单提交的时候会自动触发表单验证如果验证通过,会触发 submit 事件如果验证失败,不会触发 submit--><van-form @submit="onSubmit"><van-fieldv-model="user.mobile"name="mobile"placeholder="请输入手机号":rules="userFormRules.mobile"type="number"maxlength="11"><template #left-icon><i class="toutiao toutiao-shouji"></i></template></van-field><van-fieldv-model="user.code"name="code"placeholder="请输入验证码":rules="userFormRules.code"type="number"maxlength="6"><template #left-icon><i class="toutiao toutiao-yanzhengma"></i></template><template #button><van-button class="send-sms-btn"  native-type="button" round size="small" type="default">发送验证码</van-button></template></van-field><div class="login-btn-wrap"><van-button class="login-btn" block type="info" native-type="submit">登录</van-button></div></van-form><!-- /登录表单 --></div>
</template><script>
import { login } from '@/api/user'export default {name: 'LoginIndex',components: {},props: {},data () {return {user: {mobile: '', // 手机号code: '' // 验证码},userFormRules: {mobile: [{required: true,message: '手机号不能为空'}, {pattern: /^1[3|5|7|8]\d{9}$/,message: '手机号格式错误'}],code: [{required: true,message: '验证码不能为空'}, {pattern: /^\d{6}$/,message: '验证码格式错误'}]}}},computed: {},watch: {},created () {},mounted () {},methods: {async onSubmit () {// 1. 获取表单数据const user = this.user// TODO: 2. 表单验证// 3. 提交表单请求登录this.$toast.loading({message: '登录中...',forbidClick: true, // 禁用背景点击duration: 0 // 持续时间,默认 2000,0 表示持续展示不关闭})try {const res = await login(user)console.log('登录成功', res)this.$toast.success('登录成功')} catch (err) {if (err.response.status === 400) {this.$toast.fail('手机号或验证码错误')} else {this.$toast.fail('登录失败,请稍后重试')}}// 4. 根据请求响应结果处理后续操作}}
}
</script><style scoped lang="less">
.login-container {.toutiao {font-size: 37px;}.send-sms-btn {width: 152px;height: 46px;line-height: 46px;background-color: #ededed;font-size: 22px;color: #666;}.login-btn-wrap {padding: 53px 33px;.login-btn {background-color: #6db4fb;border: none;}}
}
</style>

#5、验证码处理

#5.1 验证手机号

验证按钮绑定事件

<van-buttonclass="send-sms-btn"native-type="button"roundsize="small"type="default"@click="onSendSms"  // <== 增加绑定事件
>发送验证码</van-button>

给表单增加ref属性

<van-form ref="loginForm" @submit="onSubmit">

1

定义事件函数,验证手机号

async onSendSms () {console.log('onSendSms')// 1. 校验手机号try {await this.$refs.loginForm.validate('mobile') // 主动校验手机号是否正确,验证失败返回Promise的reject状态} catch (err) {return console.log('验证失败', err)}// 2. 验证通过,显示倒计时// 3. 请求发送验证码
}

#5.2 使用倒计时组件

1、在 data 中添加数据用来控制倒计时的显示和隐藏

data () {return {// 其他...isCountDownShow: false   // 是否展示倒计时}
}

2、使用倒计时组件

<van-fieldv-model="user.code"name="code"placeholder="请输入验证码":rules="userFormRules.code"type="number"maxlength="6"><template #left-icon><i class="toutiao toutiao-yanzhengma"></i></template><template #button><!--倒计时组件  time 时长,毫秒数; format 格式; finish 结束事件--><van-count-downv-if="isCountDownShow"  // <== 条件判断:time="1000 * 60"  format="ss s"@finish="isCountDownShow = false"/><!--发送按钮--><van-buttonv-else   // <== 条件判断class="send-sms-btn"native-type="button"roundsize="small"type="default"@click="onSendSms">发送验证码</van-button></template></van-field>

3、发送按钮点击,倒计时显示

// 发送验证码事件
async onSendSms () {console.log('onSendSms')// 1. 校验手机号try {await this.$refs.loginForm.validate('mobile') // 主动校验手机号是否正确,验证失败返回Promise的reject状态} catch (err) {return console.log('验证失败', err)}// 2. 验证通过,显示倒计时this.isCountDownShow = true  // <========【增加这一句】// 3. 请求发送验证码
}

#5.3 发送验证码

1、在 api/user.js 中添加封装数据接口

export const sendSms = mobile => {return request({method: 'GET',url: `/v1_0/sms/codes/${mobile}`})
}

2、导入数据请求方法 /src/views/login/index.vue

import { 其他, sendSms } from '@/api/user'

1

3、发送处理/src/views/login/index.vue

async onSendSms () {// 1. 校验手机号try {await this.$refs.loginForm.validate('mobile')} catch (err) {return console.log('验证失败', err)}// 2. 验证通过,显示倒计时this.isCountDownShow = true// 3. 请求发送验证码try {await sendSms(this.user.mobile)this.$toast('发送成功')} catch (err) {// 发送失败,关闭倒计时,显示发送按钮this.isCountDownShow = falseif (err.response.status === 429) {this.$toast('发送太频繁了,请稍后重试')} else {this.$toast('发送失败,请稍后重试')}}
}

#6 、处理用户 Token

Token 是用户登录成功之后服务端返回的一个身份令牌,在项目中的多个业务中需要使用到:

  • 访问需要授权的 API 接口
  • 校验页面的访问权限
  • ...

但是我们只有在第一次用户登录成功之后才能拿到 Token。

所以为了能在其它模块中获取到 Token 数据,我们需要把它存储到一个公共的位置,方便随时取用。

往哪儿存?

  • 本地存储

    • 获取麻烦
    • 数据不是响应式
  • Vuex 容器(推荐)
    • 获取方便
    • 响应式的

使用容器存储 Token 的思路:

  • 登录成功,将 Token 存储到 Vuex 容器中

    • 获取方便
    • 响应式
  • 为了持久化,还需要把 Token 放到本地存储
    • 持久化

下面是具体实现。

1、在 src/store/index.js 中

import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)const TOKEN_KEY = 'TOUTIAO_USER'export default new Vuex.Store({state: {// 一个对象,存储当前登录用户信息(token等数据)user: JSON.parse(window.localStorage.getItem(TOKEN_KEY))// user: null},mutations: {setUser (state, data) {state.user = data// 为了防止刷新丢失,我们需要把数据备份到本地存储window.localStorage.setItem(TOKEN_KEY, JSON.stringify(state.user))}},actions: {},modules: {}
})

2、src/views/login/index.vue 登录成功以后将后端返回的 token 相关数据存储到容器中

async onSubmit () {// 1. 展示登陆中 loadingthis.$toast.loading({message: '登录中...',forbidClick: true, // 禁用背景点击duration: 0 // 持续时间,默认 2000,0 表示持续展示不关闭})// 2. 请求登录try {const { data } = await login(this.user)// 存储数据【存储到仓库里面】this.$store.commit('setUser', data.data)  this.$toast.success('登录成功')// 登录成功,跳转回原来页面// back 的方式不严谨,后面讲功能优化的时候再说this.$router.back()} catch (err) {if (err.response.status === 400) {this.$toast.fail('手机号或验证码错误')} else {this.$toast.fail('登录失败,请稍后重试')}}
}

#7、优化封装本地存储操作模块

创建 src/utils/storage.js 模块。

// 读取数据
export const getItem = key => {const data = window.localStorage.getItem(key)try {return JSON.parse(data)} catch (err) {return data}
}
// 存储数据
export const setItem = (key, value) => {if (typeof value === 'object') {value = JSON.stringify(value)}window.localStorage.setItem(key, value)
}
// 删除数据
export const removeItem = key => {window.localStorage.removeItem(key)
}

改造store/index.js里面,使用封装的方法

import { setItem,getItem } from "@/utils/storage"// user: JSON.parse(window.localStorage.getItem(TOKEN_KEY)) // 删除user: getItem(TOKEN_KEY)  // 修改为这一句// window.localStorage.setItem(TOKEN_KEY, JSON.stringify(state.user)) // 删除setItem(TOKEN_KEY,state.user) // 修改为这一句

#8、关于 Token 过期问题

登录成功之后后端会返回两个 Token:

  • token:访问令牌,有效期2小时
  • refresh_token:刷新令牌,有效期14天,用于访问令牌过期之后重新获取新的访问令牌

我们的项目接口中设定的 Token 有效期是 2 小时,超过有效期服务端会返回 401 表示 Token 无效或过期了。

为什么过期时间这么短?

  • 为了安全,例如 Token 被别人盗用

过期了怎么办?

  • 让用户重新登录,用户体验太差了
  • 使用 refresh_token 解决 token 过期

如何使用 refresh_token 解决 token 过期?

到课程的后面我们开发的业务功能丰富起来之后,再给大家讲解 Token 过期处理。

大家需要注意的是在学习测试的时候如果收到 401 响应码,请重新登录再测试

概述:服务器生成token的过程中,会有两个时间,一个是token失效时间,一个是token刷新时间,刷新时间肯定比失效时间长,当用户的 token 过期时,你可以拿着过期的token去换取新的token,来保持用户的登陆状态,当然你这个过期token的过期时间必须在刷新时间之内,如果超出了刷新时间,那么返回的依旧是 401。

处理流程:

  1. 在axios的拦截器中加入token刷新逻辑
  2. 当用户token过期时,去向服务器请求新的 token
  3. 把旧的token替换为新的token
  4. 然后继续用户当前的请求

【此操作,我们等到项目后期完成所有功能后,再去实现】在请求的响应拦截器中统一处理 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://toutiao-app.itheima.net" // 基础路径
});/*** 配置处理后端返回数据中超出 js 安全整数范围问题*/
request.defaults.transformResponse = [function(data) {try {return jsonBig.parse(data);} catch (err) {return data}}
];// 请求拦截器
request.interceptors.request.use(function(config) {const user = store.state.user;if (user) {config.headers.Authorization = `Bearer ${user.token}`;}// Do something before request is sentreturn config;},function(error) {// Do something with request errorreturn 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 datareturn 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_tokenconst user = store.state.user;if (!user || !user.refresh_token) {router.push("/login");// 代码不要往后执行了return;}// 如果有refresh_token,则请求获取新的 tokentry {const res = await axios({method: "PUT",url: "http://toutiao-app.itheima.net/v1_0/authorizations",headers: {Authorization: `Bearer ${user.refresh_token}`}});// 如果获取成功,则把新的 token 更新到容器中console.log("刷新 token  成功", res);store.commit("setUser", {token: res.data.data.token, // 最新获取的可用 tokenrefresh_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;

头条案例登录注册功能相关推荐

  1. 注册登录案例用MVC和mysql_用MVC模式实现简单用户登录注册功能

    Model2模式 Jsp+Servlet+JavaBean MVC:开发模式 M:Model 模型层 ----> JavaBean V:View 视图层 ----> Jsp C:Contr ...

  2. 登录注册功能的实现详解(多用户名注册、案例补充)

    登录注册功能的实现详解(多用户名注册.案例补充) 案例功能说明 1.可以保存多个注册用户 2.注册时判断用户名是否存在,不存在可注册 3.注册成功后,跳转到登录页面 4.取出cookie里面的值,进行 ...

  3. java wed登录面 代码_JavaWeb实现用户登录注册功能实例代码(基于Servlet+JSP+JavaBean模式)...

    下面通过通过图文并茂的方式给大家介绍JavaWeb实现用户登录注册功能实例代码,一起看看吧. 一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBea ...

  4. java实现用户登录注册功能(用集合框架来实现)

    需求:实现用户登录注册功能(用集合框架来实现) 分析: A:需求的类和接口 1.用户类 UserBean 2.用户操作方法接口和实现类 UserDao UserDaoImpl 3.测试类 UserTe ...

  5. node mysql登录注册_图解NodeJS实现登录注册功能

    该Demo根据菜鸟教程的练手项目,请提前到菜鸟教程的官网查看nodejs的相关教程,根据教程实际操作一遍,然后自己动手去实现登录.注册功能,此Demo只作参考,不符合前端相关规范. 使用的技术栈 no ...

  6. php微信网页开发实现自动登录注册功能实例

    功能:自动登录注册功能 描述:php实现微信网页自动登录注册功能 范围:适用于所有php版本 功能实例 $token = $_COOKIE['wechat_token']; if($token){// ...

  7. spring 3.x 学习笔记_spring mvc、spring jdbc 实现网站的登录注册功能

    使用spring mvc.spring jdbc 实现网站的登录注册功能 1.        据业务模型 创建model 一般实现序列化 2.        用spring 注解(@Repositor ...

  8. html登陆注册功能实现,实现用户的登录注册功能

    在基于Spring Boot框架上,实现用户的登录注册功能, 首先分析前期所需要的规划. 1 实现登录注册 前端向后端发起post请求 2用户密码安全性 密码是不推荐明文入库的,在后台采取秘钥加加不可 ...

  9. MVC框架实现用户的登录注册功能

    MVC是什么? MVC,全称model view controller-模型,视图,控制器 model是将数据库中的表到Java中变成实体类 view是编写JSP页面 controller是用来业务逻 ...

最新文章

  1. 【Android UI设计与开发】第13期:顶部标题栏(四)自定义ActionBar风格和样式
  2. C#代码规范 .NET程序员需要提升的修养1
  3. echarts大屏模板_完整的可视化大屏分享,科技感十足,各行业直接就能用
  4. LIBSVM在Matlab下的使用
  5. 香肠派对电脑版_6款好玩的吃鸡小游戏,和平精英、香肠派对、迷你攻势、、、...
  6. CSV用excel打开乱码
  7. Java创建Zip文件示例
  8. ARM指令计算机器码,ARM中几种把BL指令转化为机器码算法
  9. RTOS中的任务句柄到底是什么意思?
  10. DL/T645-2007通信协议应用层学习记录
  11. 携手李连杰壹基金计划 创慈善博客
  12. OA实施方法论的重要性
  13. TikTok云控系统是什么,有什么功能
  14. Lingoes安装词典和语音库
  15. 基于腾讯地图+Ant-Design-Vue封装省市区联动查询组件
  16. 堡垒机前戏:paramiko模块
  17. 数学分析教程 第十三章学习感受
  18. 干货:Web测试检查清单
  19. python开篇——初识python
  20. 道可道云在线网盘源码

热门文章

  1. 自动化1121和1122班学生链接
  2. Web 3.0让网络巨头们恐慌?Dapp爆发潮的到来会更让人颤抖!
  3. 中华女子学院计算机考试题库,中华女子学院综合素质测试面试题历年总结
  4. shell命令:ls命令
  5. 定制自己的xDoclet标签
  6. 【Gem5】有关gem5模拟器的资料导航
  7. origin 双Y轴堆积条形图
  8. python:分隔符
  9. ubuntu 20.04搭建ESP-ADF开发环境
  10. 微信小程序 | 小程序WXSS-WXML-WXS