Vue2项目之移动端购物商城(二) 登录页的实现,vant组件Toast的使用,对axios进行二次封装,将数据的请求封装成api接口,设置路由导航守卫-全局前置守卫

零.前置相关内容

1.Toast组件的使用
使用场景

在用户输入手机号后,需要给出提示,若输入正确:“发送成功,请注意查收!”,
若输入不正确:“请输入正确的手机号”

使用步骤
  • Toast轻提示组件的注册安装,步骤略(同NavBar)
  • 使用方式1----导入调用(组件或非组件内都可以):Toast("提示内容")
  • 使用方式2----通过this直接调用(组件内):this.$toast("提示内容"),

这种方法的本质是把方法注册挂载到了Vue原型上:Vue.prototype.$toast=xxx

常用用法
  • 文字提示:Toast(‘提示内容’)
  • 加载提示:
 Toast.loading(){
    message:'加载中',
    forbidClick:true
}
  • 成功/失败提示:Toast.success和Toast.fail
2.为什么要对axios进行二次封装?

axios的二次封装

省流

使用axios请求后端接口,一般都会对axios进行一些配置,比如配置基础地址,请求&响应拦截器等
所以在项目开发中,都会对axios进行基本的二次封装,单独封装到一个request模块中,便于维护使用

步骤(以获取图形验证码为例)
  • 新建封装文件utils/request.js
import axios from 'axios'
const instance = axios.create({// 创建一个axios实例instance
  baseURL: 'http://smart-shop.itheima.net/index.php?s=/api', // 注意操作文档上的基础地址是否变动
  timeout: 1000
})
// 请求拦截器:发送请求之前做些什么
instance.interceptors.request.use(config => {
  return config// 成功的回调
}, error => {
  return Promise.reject(error)// 失败的回调
})
// 响应拦截器:接收返回数据之前做些什么
instance.interceptors.response.use(res => {
  return res.data// axios返回数据默认多包一层data,提前剥掉
}, err => {
  return Promise.reject(err)
})
// 导出实例
export default instance
  • 在页面中试调用(需提前查看阅读文档,获取请求方式,请求路径和请求参数)
    在这里插入图片描述
import request from '@/utils/request.js'
data () {
  return {
    picUrl: '',
    picKey: ''
  }
},
async created () {
   // const res = await request.get('/captcha/image')
    //console.log('获取到的数据:', res)//获取到data,message,status三个属性
    const { data: { base64, key } } = await request.get('/captcha/image') // 把需要的res对象解构出来
    this.picUrl = base64
    this.picKey = key
},
  • 用获取到的数据动态渲染登录页面:
<div class="form-item">
    <input type="text" class="inp" maxlength="5" placeholder="请输入图形验证码">
    <!-- <img src="https://img0.baidu.com/it/u=1838409809,2846524470&fm=253&fmt=auto&app=138&f=JPEG?w=443&h=236"> -->
    <img :src="picUrl">
</div>

在这里插入图片描述

3.为什么以及如何将请求封装成api接口?

在中大型项目中,最好把请求封装成方法,统一存放到api模块,与页面分离
这么做的理由是:
       在上例中,若在页面中类似的请求多了起来,整个页面会充斥着请求的代码,可阅读性较差,而且相同的请求也没有被复用,更没有被统一管理
统一放到api模块后:
       将实现请求和页面的分离,复用相同请求并对请求统一管理

步骤:(优化上例)

  • 新建请求模块并封装:把之前在页面中发送的请求放在新文件src/api/login.js中封装起来
import request from "@/utils/request"
export const getPicCode=()=>{//起一个方法名,以与后面同页面的其他请求做区分
   return request.get('/captcha/image');//必须要return,不然获取不到结果
}
  • 在页面中Login/index.js按需导入并调用
import { getPicCode } from '@/api/login.js'
async created () {
    const { data: { base64, key } } = await getPicCode()
    this.picUrl = base64
    this.picKey = key
  },
}
  • 优化:考虑到在created中将不止发送一次数据请求,可以把方法写在methods中然后再在created中调用
  import { getPicCode } from '@/api/login.js'
  created () {
    this.getPicCodeMethods()
  },
  methods: {
    async getPicCodeMethods () {
      const { data: { base64, key } } = await getPicCode()
      this.picUrl = base64
      this.picKey = key
    }
  },

一.登录页静态布局

0.login页分为上下两部分

上面–导航条:使用vant的NavBar作为通用组件,(在几乎每个页面都使用)
下面–主题部分:自定义

1.NavBar组件的使用
  • 在官网上查用法(属性和属性值)
  • 在utils/vant-ui.js中按需引入
import { NavBar } from 'vant'
Vue.use( NavBar )
  • 在Login页中使用
<van-nav-bar title="会员登录" left-arrow @click-left="$router.go(-1)" />
2.登录页主体部分
<template>
  <div class="login">
    <van-nav-bar title="会员登录" left-arrow @click-left="$router.go(-1)" />
    <div class="container">
      <div class="title">
        <h3>手机号注册</h3>
        <p>未注册的手机号登录后将自动注册</p>
      </div>
      <div class="form">
        <div class="form-item">
          <input type="text" class="inp" maxlength="11" placeholder="请输入手机号码">
          </div>
        <div class="form-item">
          <input type="text" class="inp" maxlength="5" placeholder="请输入图形验证码">
          <img src="https://img0.baidu.com/it/u=1838409809,2846524470&fm=253&fmt=auto&app=138&f=JPEG?w=443&h=236">
          </div>
        <div class="form-item">
          <input type="text" class="inp" maxlength="5" placeholder="请输入短信验证码">
          <button>获取验证码</button>
          </div>
      </div>
      <div class="login-btn">登录</div>
    </div>
  </div>
</template>

效果:
在这里插入图片描述

二.图片验证码相关功能

图片验证码的本质是后台返回的照片,目的是强制人和机器之间的交互,抵御自动化攻击(如:避免批量请求获取短信)

1.封装API接口:获取图片验证码

略,详见:3.为什么以及如何将请求封装成api接口?

2.对文本框输入的手机号码和图片验证码进行正则校验
//step1:分别在手机号和图形验证码的input框中,使用v-model双向绑定在data中声明的变量moblie和picCode
//step2:在函数isVaild中分别对两个变量进行正则校验,当它们格式错误时返回false
/*login/index.vue/methods*/
    isValid () { // 正则校验:手机号和图形验证码
      // 1.手机号的正则:以1开头,第2位3`9,一共11位数字
      if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
        this.$toast('请输入正确的手机号!')
        return false
      }
      // 2.图形验证码的正则:4个字符
      if (!/^\w{4}/.test(this.picCode)) {
        this.$toast('请输入正确的图形验证码!')
        return false
      }
      // 其他情况则通过
      return true
    },
//step3:调用isValid函数
   	//在哪里调用?==>点击发送短信,或点击登录按钮时调用(后续内容)

三.短信验证码相关功能

1.实现倒计时效果:点击"获取验证码",开始倒计时59s

业务要求:

  • 输入的手机号和图形验证码要正确
  • 1分钟内倒计时只会触发一次
  • 按钮的文字不是固定的,倒计时开始后变成"重新获取59s"

思路:
对"获取验证码"按钮绑定点击事件getCode,声明三个新变量totalSec,nowSec,timer,分别代表总秒数,当前秒数和定时器的名字
判断:当前timer定时器并未开启,且总秒数=当前秒数时,开启定时器
让nowSec每秒自减,当它的值小于等于0时清空定时器并初始化变量

    // 点击开启倒计时
    getCode () {
      if(!this.isVaild()) return//手机号和图形验证码格式不对,不继续往下了
      if (!this.timer && this.totalSec === this.nowSec) {
        this.$toast('短信发送成功,请注意查收!')
        this.timer = setInterval(() => {// 1分钟内倒计时只会触发一次
          console.log(this.nowSec--)
          if (this.nowSec <= 0) {
            clearInterval(this.timer)
            this.timer = null
            this.nowSec = this.totalSec
          }
        }, 1000)
      }

优化:"获取验证码"按钮的文本从写死变成动态渲染

<button @click="getCode">{{this.nowSec==this.totalSec?'获取验证码':`重新获取${this.nowSec}`}}</button>

优化:为避免性能浪费,加入业务逻辑—当用户离开登录页时,关闭定时器

  destroyed () {
    clearInterval(this.timer)
  }
2.封装API接口:获取短信验证码
  • 查看接口文档,确定请求方式,请求路径,请求参数
    在这里插入图片描述在这里插入图片描述
  • 封装请求
//api/login.js
......
//获取短信验证码
export const getMsgCode=(captchaCode, captchaKey, mobile)=>{
	return request.post('/captcha/sendSmsCaptcha',{
		form:{
			captchaCode, //图形验证码
			captchaKey,	//图形验证码key
			 mobile	//手机号
		}
	)
}
  • 页面调用
import { getPicCode, getMsgCode } from '@/api/login.js'
...
methods:{
	async getMsgCodeMethods(){//在getCode方法中调用
		const res=await getMsgCode(this.picCode,this.picKey,this.mobile);
	}
}

. 在这里插入图片描述

四.登录相关功能

1.封装API接口:验证码登录请求
  • 查看接口文档,确定请求路径,请求方式,请求参数
    在这里插入图片描述
    在这里插入图片描述
    此处为啥图形验证码不用传?
为什么只要手机号mobile和短信验证码smsCode进行校验?
图形验证码已经传过了(在点击获取短信验证码之前,图片验证码要填对),用于获取短信验证码的时候
----只要能拿到短信验证码就说明图形验证码已经验证过了
  • 封装请求
//api/login.js

....
// 验证码登录请求
export const codeLogin = (mobile, smsCode) => {
  return request.post('/passport/login', {
    form: {
      // 无关参数--给默认值
      isParty: false,
      partyData: {},
      // 有关参数--传参
      mobile,
      smsCode
    }
  })
}
  • 页面调用
import { getPicCode, getMsgCode, codeLogin } from '@/api/login.js'
...
// 页面调用:验证码登录
    async codeLoginMethods () {
      const res = await codeLogin(this.mobile, this.smsCode)
      console.log('接收的用户登录信息res:', res.data)
    },
//在哪里调用?
	//后续:点击登录按钮时调用:this.codeLoginMethods()

踩坑:res.data里面什么也没有.原因:短信验证码的参数名定义为smsCode ,调用时写成了msgCode
正确输出:userIdtoken
在这里插入图片描述

2.点击登录:登录前校验+获取登录用户数据+登录成功后跳转
/*输入短信验证码的文本框双向绑定smsCode*/
    // 点击登录
    login () {
      if (!this.isVaild()) return // 正则校验:手机号和图形验证码
      if (!/^\d{6}$/.test(this.smsCode)) { // 正则校验:短信验证码
        this.$toast('请输入正确的短信验证码')
        return
      }
      this.codeLoginMethods()// 调用方法存入用户信息:获取到了token和userId,后面有用
      this.$router.push('/')//登录成功后跳转
    }

Tips:校验阶段data中的变量写上默认值,免得每次验证都手动输入手机号和验证码

五.优化

1.使用响应拦截器对接口的错误提示统一处理
为什么在响应拦截器处理错误?

在短信验证码功能和点击登录功能等业务功能的实现中,错误情况没有处理

当用户发送的请求被响应(响应1,响应2,响应3…)回来的时候,在被业务代码处理之前,
响应拦截器作为返回数据的第一个数据流转站,可以在里面统一处理错误,才会交还给业务代码

如何处理?
// 响应拦截器:接收返回数据之前做些什么
instance.interceptors.response.use(res => {
  // 统一处理错误:对状态码做出判断
  if (res.data.status !== 200) {
    Toast(res.data.message)// 1.给提示
    return Promise.reject(res.data.message)// 2.抛出一个错误
  } else {
    return res.data// axios返回数据默认多包一层data,提前剥掉
  }
}, err => {
  return Promise.reject(err)
})
2.使用vuex存储登录认证的信息

最后获取到的用户的token和userId,应该用vuex构建user模块,以一个对象的形式存起来

  • 创建store/modules/user.js子模块
export default {
	namespaced:true,//声明命名空间
	state(){
		return{
			userInfo:{
				userId:'',
				token:''
			}
		}
	},
	mutations:{
		setUserInfo(state,obj){
			state.userInfo=obj
		}
	}
}
  • store/index.js中引入子模块user.js
import user from '@/store/modules/user'
...
modules:{
	user
}
  • 调用,将在页面中获取到的res.data存到userInfo

Login/index.vue

    // 页面调用:验证码登录
    async codeLoginMethods () {
      const res = await codeLogin(this.mobile, this.smsCode)
      // console.log('接收的用户登录信息res:', res.data)//返回一个对象,有userId和token属性
      this.$store.commit('user/getUserInfo', {
        token: res.data.token,
        userId: res.data.userId
      })
      console.log(this.$store.state.user.userInfo)// 存入成功
    },
3.使用storage对vuex数据进行持久化处理

现阶段vuex数据刷新就会丢失,这不符合项目需求,需要将token存本地
做法是创建storage模块,封装本地存储的增删改查的操作

  • step1:封装store/modules/storage.js模块
const infoKey=''//声明存入对象
// 存
export const setInfo=(info)=>{
	localStorage.setItem('infokey',JSON.stringifg(info))
}
// 取
export const getItem=()=>{
	const result = localStorage.getItem(infoKey)
	return result?JSON.parse(result):{userId:'',token:''}
}
// 删
const const removeItem=()=>{
	localstorage.removeItem(infoKey)
}
  • step2:在modules/user.js中调用
//按需导入
import { setInfo , getInfo } from '@store/modules/storage'
...
state(){
	return{
	  // userInfo: {
      //   userId: '',
      //   token: ''
      // }
      userInfo: getInfo()
	}
},
mutations: {
    setUserInfo (state, obj) {
      state.userInfo = obj// 存入vuex
      setInfo(obj)// 存入本地
    }
  }

在这里插入图片描述

4.使用Toast.loading() 添加loading效果

使用loading提示优化页面,可以避免请求的时候出现因为网络问题而没有响应的情况,此外它还有这些优化效果:

  • 节流处理:防止用户在一次请求后,数据还没回来之前,多次点击,发送无效请求
  • 友好提示:告知用户在加载中,优化用户体验

loading提示可以在多个场景中使用(添加购物车,用户结算,点击登录等),因此可以在拦截器中设置loading效果

  • 在请求拦截器中,每次请求,就打开loading
  • 在响应拦截器中,每次响应,就关闭loading
// utils.request.js 完整代码
import axios from 'axios'
import { Toast } from 'vant'

// 创建一个axios实例instance
const instance = axios.create({
  // 注意操作文档上的基础地址是否变动
  baseURL: 'http://smart-shop.itheima.net/index.php?s=/api',
  timeout: 1000,
  headers: { platform: 'H5' }// 状态码返回500的时候添加该请求头
})

// 请求拦截器:发送请求之前做些什么
instance.interceptors.request.use(config => {
  // 开启loading,禁止背景点击
  Toast.loading({
    message: '加载中...',
    forbidClick: true, // 禁止背景点击
    loadingType: 'spinner', // 图标
    duration: 0// 不会自动消失
  })
  return config// 成功的回调
}, error => {
  return Promise.reject(error)// 失败的回调
})

// 响应拦截器:接收返回数据之前做些什么
instance.interceptors.response.use(res => {
  // 统一处理错误:对状态码做出判断
  if (res.data.status !== 200) { // 状态码错误
    Toast(res.data.message)// 1.给提示
    return Promise.reject(res.data.message)// 2.抛出一个错误
  } else { // 状态码正确
    // 关闭loading
    Toast.clear()
  }
  return res.data// axios返回数据默认多包一层data,提前剥掉
}, err => {
  return Promise.reject(err)
})

// 导出实例
export default instance

5.使用全局前置守卫实现对页面的访问拦截
场景

商城页面中大部分页面都能进行游客访问,仅在需要登录才能进行的操作,会提示并跳转到登录页.但在需要用户双概述才能访问的页面比如支付页,订单页等,游客不能进入该页面.需要做拦截处理

思路
  • 所有的路由一旦被匹配到,都会经过全局前置守卫
  • 只有全局前置守卫放行,才会真正解析渲染组件,才能看到页面内容

如何判断拦截还是放行?
    用户是否有登录权证token

   跳转路由时,触发全局前置守卫(判断:是否访问权限页面)--直接放行
    是--判断:是否有token?--直接放行
        无--拦截到登录
语法

官网位置:https://v3.router.vuejs.org/zh/guide/advanced/navigation-guards.html

router.beforeEach((to,from,next)=>{
    //1.to:往哪里去,到哪去的路由信息对象
    //2.from:从哪里来,从哪来的路由信息对象
    //3.next():是否放行
        - next()调用--放行
        - next(路径)--拦截:转到某个路径页面
})
代码实现
  • router/index.js
const authUrls = ['/pay', '/myorder']// 定义一个数组用于存放权限页面
// 全局前置守卫
router.beforeEach((to, from, next) => {
  // 判断:访问的是权限页面吗?
  if (!authUrls.includes(to.path)) {
    next()// 直接放行
    return
  }
  // 访问权限页面时
  // 判断:有token吗?
  const token = store.state.user.userInfo.token
  // const token = store.getters.token
  console.log('获取token', token)

  if (token) { // 有:放行
    next()
  } else { // 没有:拦截并转到登录页
    next('/login')
  }
})
效果验证
  • 未登录时:地址栏输入pay页地址,回车后会自动转到登录页
  • 登录时(localStorage本地存储中有token数据):输入pay页地址可以顺利进入
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端OnTheRun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值