# 登录模块

# 回顾

  • 综合案例
    • 编辑图书(根据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 :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)
})
  • 自定义表单验证规则
  1. 定义验证规则函数(参考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()
  }
}
  1. 使用自定义验证规则
// 导入封装的表单验证方法
import { checkMobile } from '@/utils/validate.js'
{ validator: checkMobile, trigger: 'blur' }
  • 是否同意协议验证(checkbox)
  1. 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>
  1. 表单验证
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()