# 登录模块
# 回顾
- 综合案例
- 编辑图书(根据id查询图书信息填充表单;编辑提交表单)
- 生命周期
- 创建阶段;更新阶段;销毁阶段
- 黑马头条
- 熟悉项目的主题业务
- 熟悉用到的相关技术
- 初始化项目(手工选择特性)
- 基于git进行项目代码的关系
- 代码规范
- 熟悉代码规范的重要性
- ESLint负责验证代码的规则
- 规则由Standard提供
- ESLint在验证是依据Standard提供的标准
- 脚手架需要自定义验证规则
- Vscode也可以定制代码的验证规则
- 熟悉UI组件库Element-UI的用法
# 登录功能介绍
- 登录页面基本布局
- 表单输入用户名和密码
- 进行表单验证
- 点击登录按钮调用后端接口
- 后端接口返回一个状态
- 前端根据状态判断登录是否成功
- 如果成功就跳转到主页面
- 如果登录失败进行提示
# 登录页组件与路由配置
- 配置路由映射
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/views/Login'
import Home from '@/views/Home'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{path: '/', redirect: '/login'},
{path: '/login', component: Login},
{paht: '/home', component: Home}
]
})
export default router
- 将路由对象挂载到全局
src/main.js
import router from '@/router'
new Vue({
router,
render: h => h(App),
}).$mount('#app')
# 登录页面基本布局
- 安装less和less-loader依赖包
npm i less less-loader -D
- 登录页面基本布局
<div class="login">
<!-- 登录框 -->
<div class="login-wrapper">
<!-- 顶部的图片 -->
<img src="../assets/imgs/logo_index.png" alt="">
<!-- 表单内容 -->
<el-form>
<el-form-item >
<el-input placeholder='请输入手机号...'></el-input>
</el-form-item>
<el-form-item >
<el-input placeholder='请输入验证码...'></el-input>
</el-form-item>
<el-form-item>
<el-checkbox >我已经阅读和同意用户协议和隐私条款</el-checkbox>
</el-form-item>
<el-form-item>
<el-button class='login-btn' type="primary">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
- 布局样式
.login {
position: absolute;
width: 100%;
height: 100%;
background: pink;
background:url(../assets/imgs/login_bg.jpg) no-repeat top / cover;
.login-wrapper {
position: absolute;
background: #fff;
transform: translate(-50%, -50%);
width: 400px;
height: 340px;
left: 50%;
top: 50%;
padding: 0 30px;
box-sizing: border-box;
img {
width: 200px;
margin: 20px auto;
display: block;
}
.login-btn {
width: 100%;
}
}
}
# 实现登录功能
测试账号:13911111111/246810
- 安装axios
npm i axios
- 表单数据绑定
<el-input v-model="loginForm.mobile" placeholder="请输入手机号"></el-input>
<el-input v-model="loginForm.code" class='code' placeholder="请输入验证码" ></el-input>
data () {
return {
loginForm: {
mobile: '13911111111',
code: '246810'
}
}
}
- 调用接口登录
handleLogin () {
// 获取表单数据,调用接口实现表单提交
axios.post('http://api-toutiao-web.itheima.net/mp/v1_0/authorizations', this.loginForm)
.then((ret) => {
this.$router.push('/home')
})
}
# 表单验证
- 表单验证概述:对表单输入的内容进行验证
- 基于Element-UI进行表单验证
- el-form标签上绑定属性
- model用于绑定输入域的数据
- rules用于设置表单验证规则
- ref用于操作表单组件元素
- 在el-form-item标签上面通过prop绑定表单输入域名称
- 在提交表单时,必须调用validate方法
this.$refs.loginForm.validate
- el-form标签上绑定属性
<el-form :model="loginForm" :rules="loginRules" ref="loginForm">
<el-form-item prop="mobile">
loginRules: {
// key 必须和表单输入域的名称一致
mobile: [
// required 表示必须输入内容
// message 表示规则如果不匹配,就提示这个信息
// trigger 表示触发验证的条件
// 可以配置多个验证规则
{ required: true, message: '请输入手机号', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' }
]
}
this.$refs.loginForm.validate((valid) => {
// valid如果是true表示验证通过,false表示验证不通过
console.log(valid)
})
- 自定义表单验证规则
- 定义验证规则函数(参考element-ui官方网站)
- 封装独立的表单验证方法
src/utils/validate.js
- 封装独立的表单验证方法
// 验证手机号是否符合格式要求
export const checkMobile = (rule, value, callback) => {
// 自己实现表单验证的规则(正则表达式)
const regMobile = /^1[3-9]\d{9}$/
if (!regMobile.test(value)) {
// 验证错误
callback(new Error('手机号格式错误'))
} else {
// 验证正确
callback()
}
}
- 使用自定义验证规则
// 导入封装的表单验证方法
import { checkMobile } from '@/utils/validate.js'
{ validator: checkMobile, trigger: 'blur' }
- 是否同意协议验证(checkbox)
- el-checkbox基本使用
// 登录的表单数据
loginForm: {
mobile: '13911111111',
code: '246810',
// 这里的free的值不可以使用 ''
free: false
},
<el-form-item prop='agree'>
<el-checkbox v-model='loginForm.agree'>我已经阅读和同意用户协议和隐私条款</el-checkbox>
</el-form-item>
- 表单验证
agree: [
// pattern 用于正则匹配
// change 事件表示状态发生变化时触发
// 如下的正则匹配表示agree值只有为true的时候才符合要求
{ pattern: /true/, message: '请选择协议', trigger: 'change' }
]
# 按钮加载提示
效果:点击一次按钮后,禁用按钮,完成请求可以再次点击。
- 利用el-button组件的loading属性进行控制
- 默认值设置为false
- 点击按钮后修改为true
- 接口返回后再修改为false
data () {
return {
// 控制重复提交
loading: false,
handleLogin () {
// 提交表单的时候,手动触发表单验证
this.$refs.loginForm.validate(valid => {
if (valid) {
// 表单验证如果通过,修改登录按钮的状态
this.loading = true
axios.post('http://api-toutiao-web.itheima.net/mp/v1_0/authorizations', {
mobile: this.loginForm.mobile,
code: this.loginForm.code
}).then(ret => {
if (ret.data.data.token) {
this.$router.push('/home')
} else {
alert('用户名或者密码错误')
// 如果登录失败,让用户可以再次点击按钮
this.loading = false
}
}).catch(() => {
alert('用户名或者密码错误')
// 如果登录失败,让用户可以再次点击按钮
this.loading = false
})
# 异步编程
# 获取异步结果的问题
- 异步的结果无法通过返回值获取
// 异步的结果无法通过返回值获取
function getInfo () {
// 原生Ajax获取接口数据步骤
var xhr = new XMLHttpRequest()
xhr.open('get', 'http://api.zjie.wang/api/test')
xhr.send(null)
var ret = null
// 这里是函数定义还是调用?定义
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 获取正常的数据
ret = xhr.responseText
// return ret
}
}
}
return ret
}
var ret = getInfo()
console.log(ret)
# 正确获取异步结果
- 异步的结果必须通过回调函数才可以获取
// 异步编程
// 异步的结果无法通过返回值获取
// 异步的结果必须通过回调函数才可以获取
function getInfo (callback) {
// 原生Ajax获取接口数据步骤
var xhr = new XMLHttpRequest()
xhr.open('get', 'http://api.zjie.wang/api/test')
xhr.send(null)
// 这里是函数定义还是调用?定义
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 获取正常的数据
var ret = xhr.responseText
// 调用回调函数(该回调函数callback只有什么时候才会调用?得到结果后才会调用)
callback(ret)
}
}
}
}
// var ret = getInfo()
// console.log(ret)
// 我们希望在getInfo函数的外部获取结果应该如何去做?
// 空手而进(回调函数的定义),满载而归(回调函数的形参)
getInfo(function (ret) {
console.log(ret)
})
- 异步获取结果的另一个场景
// --------------------------
function getInfo (callback) {
// 一秒后返回结果
setTimeout(function () {
var data = 'hello'
// 为何回调函数可以获取异步的结果?已经只有得到异步结果后才会触发回调函数
callback(data)
}, 1000)
}
// var ret = getInfo()
// console.log(ret)
getInfo(function (ret) {
console.log(ret)
})
# 回调地狱问题
// 回调的问题
function getInfo1 (callback) {
setTimeout(function () {
var data = 'tom'
callback(data)
}, 1000)
}
function getInfo2 (callback) {
setTimeout(function () {
var data = 'jerry'
callback(data)
}, 2000)
}
function getInfo3 (callback) {
setTimeout(function () {
var data = 'spike'
callback(data)
}, 3000)
}
// getInfo1(function (ret) {
// console.log(ret)
// })
// getInfo2(function (ret) {
// console.log(ret)
// })
// getInfo3(function (ret) {
// console.log(ret)
// })
// 需求:打印的顺序是 spike -> tom -> jerry
getInfo3(function (ret) {
console.log(ret)
getInfo1(function (ret) {
console.log(ret)
getInfo2(function (ret) {
console.log(ret)
})
})
})
// 如果代码嵌套层次非常多,就会出现【回到地狱 callback hell】
// 代码的可读性较差,所以需要改进,
// 所以就诞生了Promise的改进方案
// 但是Promise方式也不是最好的,所以后来就有了Async函数(asynnc/await)
# Promise基本用法
- Promise是回调函数的改进用法(Promise是回调函数的语法糖)
function getInfo () {
var p = new Promise(function (resolve, reject) {
// 这里用于处理异步的任务
var xhr = new XMLHttpRequest()
xhr.open('get', 'http://api.zjie.wang/api/test1')
xhr.send(null)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var ret = xhr.responseText
// resolve调用时用于得到正确结果
resolve(ret)
} else {
// reject调用时用于得到错误结果
reject('服务器出错了')
}
}
}
})
return p
}
// 在getInfo函数的外部获取内部的异步结果
getInfo()
.then(function (ret) {
// 获取正确的结果
console.log(ret)
})
.catch(function (err) {
// 获取错误的结果
console.log(err)
})
# Promise解决回调地狱问题
- Promise使回调地狱的代码变得可读性更高
// 回调的问题:用Promise方式解决
function getInfo1 (callback) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
var data = 'tom'
if (data) {
// 正确结果
resolve(data)
} else {
// 错误结果
reject('获取tom失败')
}
}, 1000)
})
}
function getInfo2 (callback) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
var data = 'jerry'
if (data) {
// 正确结果
resolve(data)
} else {
// 错误结果
reject('获取jerry失败')
}
}, 2000)
})
}
function getInfo3 (callback) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
var data = 'spike'
if (data) {
// 正确结果
resolve(data)
} else {
// 错误结果
reject('获取spike失败')
}
}, 3000)
})
}
// 需求:打印的顺序是 spike -> tom -> jerry
getInfo3()
.then(function (ret) {
console.log(ret)
return getInfo1()
})
.then(function (ret) {
console.log(ret)
return getInfo2()
})
.then(function (ret) {
console.log(ret)
})
.catch(function (err) {
// 前面任何一个then中发生异常,后续的then就不再执行了,而是直接进入最后的catch中
console.log(err)
})
# 关于then的返回值问题
- 1、如果then中返回Promise实例对象,那么下一个then会得到该异步任务的结果
- 2、如果then中不返回任何信息,那么默认会返回一个Promise实例对象,但是没有值
- 3、如果then中返回普通的数据,那么下一个then可以直接得到该数据
// 需求:打印的顺序是 spike -> tom -> jerry
// 所有的then中的返回值其实都是Promise实例对象,所以始终支持then的链式操作
// then中return的Promise实例对象实际上是一个新的任务(上一个任务已经结束了)
getInfo3()
.then(function (ret) {
console.log(ret)
// 1、如果then中返回Promise实例对象,那么下一个then会得到该异步任务的结果
return getInfo1()
})
.then(function (ret) {
console.log(ret) // tom
return getInfo2()
})
.then(function (ret) {
console.log(ret)
// 2、如果then中不返回任何信息,那么默认会返回一个Promise实例对象,但是没有值
// return new Promise(function (resolve) {
// resolve(undefined)
// })
// 3、如果then中返回普通的数据,那么下一个then可以直接得到该数据
// 实际上普通数据会默认包装为Promise实例对象并返回
return 'hello'
// return new Promise(function (resolve) {
// resolve('hello')
// })
})
.then(function (ret) {
console.log(ret + '--------------------')
})
.catch(function (err) {
// 前面任何一个then中发生异常,后续的then就不再执行了,而是直接进入最后的catch中
console.log(err)
})
# Async函数基本用法
- async函数可以进一步改进Promise编程体验
- await关键字必须出现在async函数中
- await之后一般是跟着Promise实例对象,await左侧可以直接获取到该Promise的结果
- 本来通过then方式获取的结果,现在可以通过await获取
// Async函数基本用法
function getInfo () {
return new Promise(function (resolve, reject) {
// 这里用于处理异步的任务
var xhr = new XMLHttpRequest()
xhr.open('get', 'http://api.zjie.wang/api/test')
xhr.send(null)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var ret = xhr.responseText
// resolve调用时用于得到正确结果
resolve(ret)
} else {
// reject调用时用于得到错误结果
reject('服务器出错了')
}
}
}
})
}
async function getData () {
// Promise方式获取结果
// getInfo().then(ret => {
// console.log(ret)
// })
// -------------------------
// async函数获取结果
// await关键字必须出现在async函数中
// await之后一般是跟着Promise实例对象,await左侧可以直接获取到该Promise的结果
// 本来通过then方式获取的结果,现在可以通过await获取
var ret = await getInfo()
console.log(ret)
}
getData()