53 - 综合案例 - 智慧商城-05 - 登录页静态布局

一. 登录页静态布局

1.准备工作

        (1). src新建 'styles/common.less',重置默认样式

// 重置默认样式
*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

// 文字溢出省略号
.text-ellipsis-2{
    overflow:hidden;
    -webkit-line-clamp: 2;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient: vertical;
}

        (2). main.js导入 common.less

import '@/styles/common.less'

        (3) 图片素材拷贝到 assets 目录[备用]

        

        

2.登录页静态布局编写
        (1). 头部组件说明(NavBar)

                ①. utils / vant-ui.js 按需引入组件

import { NavBar } from 'vant'

Vue.use(NavBar)

                ②. 页面使用NavBar组件

<!--login / index.vue文件-->
  <div class="login">
    <!-- 头部 NavBar-->
    <!-- $router.go(-1): 返回上一页 -->
    <van-nav-bar title="会员登录" left-arrow @click-left="$router.go(-1)" />

    ...
 </div>
        (2). 通用样式覆盖
// common.less 增加导航样式css

// 添加导航的通用样式
.van-nav-bar{
    .van-nav-bar__arrow{
        color: #333;
    }
}
        (3). 登录静态结构编写
<!-- login / index.vue -->
<template>
  <div class="login">
    <!-- 头部 NavBar-->
    <!-- $router.go(-1): 返回上一页 -->
    <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 class="inp" maxlength="11" placeholder="请输入手机号" type="text">
        </div>
        <div class="form-item">
          <input class="inp" maxlength="5" placeholder="请输入图形验证码" type="text">
          <img src="@/assets/code.jpg" alt="">
        </div>
        <div class="form-item">
          <input class="inp" placeholder="请输入短信验证码" type="text">
          <button>获取验证码</button>
        </div>
      </div>

      <div class="login-btn">登录</div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'LoginIndex'
}
</script>
<style lang="less" scoped>
.container {
  padding: 49px 29px;

  .title {
    margin-bottom: 20px;

    h3 {
      font-size: 26px;
      font-weight: normal;
    }

    p {
      line-height: 40px;
      font-size: 14px;
      color: #b8b8b8;
    }
  }

  .form-item {
    border-bottom: 1px solid #f3f1f2;
    padding: 8px;
    margin-bottom: 14px;
    display: flex;
    align-items: center;

    .inp {
      display: block;
      border: none;
      outline: none;
      height: 32px;
      font-size: 14px;
      flex: 1;
    }

    img {
      width: 94px;
      height: 31px;
    }

    button {
      height: 31px;
      border: none;
      font-size: 13px;
      color: #cea26a;
      background-color: transparent;
      padding-right: 9px;
    }
  }

  .login-btn {
    width: 100%;
    height: 42px;
    margin-top: 39px;
    background: linear-gradient(90deg, #ecb53c, #ff9211);
    color: #fff;
    border-radius: 39px;
    box-shadow: 0 10px 20px 0 rgba(0, 0, 0, .1);
    letter-spacing: 2px;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}</style>
         (4). 代码示例

 

 二. request模块-axios封装

目标:将axios 请求方法,封装到 request 模块

使用 axios 来请求后端接口,一般都会对 axios 进行 一些配置(比如:配置基础地址,请求响应拦截器等

所以项目开发中,都会对 axios 进行基本的二次封装,单独封装到一个request 模块中,便于维护使用

        1. 安装axios
npm i axios

// 报错使用 npm i axios --legacy-peer-deps
        2. 新建request模块
// utils/request.js

import axios from 'axios'
// 创建 axios 实例,将来对创建出来的实例,进行自定义配置
// 好处: 不会污染原始的 axios 实例
const instance = axios.create({
  baseURL: 'http://cba.itlike.com/public/index.php?s=/api/', // 主机地址
  timeout: 5000
})

// 自定义配置 - 请求/响应 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
  // 对响应数据做点什么(默认axios会多包装一层data,需要响应拦截其中处理一下)
  return response.data
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error)
})

// 导出配置好的实例
export default instance
        3. 页面中使用
// login/index.vue

<template>
...
</template>

<script>
// 导入封装的axios
import request from '@/utils/request'
export default {
  name: 'LoginIndex',
  // 发送请求
  async created () {
    const res = await request.get('/captcha/image')// 接口地址
    console.log(res)
  }
}
</script>

<style lang="less" scoped>
...
</style>
        4. 代码示例

三. 图形验证码功能完成

目标:基于请求回来的 base64 图片,实现图形验证码功能

说明:

        (1).图形验证码,本质就是一个请求回来的图片

        (2).用户将来输入图形验证码,用于强制人机交互,可以抵御机器自动化攻击(例如:避免批量请求获取短信)

需求:

        (1).动态将请求回来的 base64 图片,解析染出来

        (2).点击验证码图片盒子,要刷新验证码

1. 发送请求,获取验证码信息
// login/index.vue

<template>
  ...
          <!-- v-model绑定用户输入的验证码 -->
          <input v-model="picCode" class="inp" maxlength="5" placeholder="请输入图形验证码" type="text">
          <!-- 使用图形验证码,点击调用请求方法 -->
          <img v-if="picUrl" :src="picUrl" @click="getPicCode" alt="">
        ...
</template>
<script>
import request from '@/utils/request'
export default {
  name: 'LoginIndex',
  data () {
    return {
      picCode: '', // 用户输入的图形验证码
      picKey: '', // 将来请求传递的图形验证码唯一标识
      picUrl: ''// 存储请求渲染的图片地址
    }
  },
  created () {
    this.getPicCode()// 调用发送请求方法
  },
  methods: {
    // 发送请求封装为一个方法
    async getPicCode () {
      const { data: { base64, key } } = await request.get('/captcha/image')// 接口地址
      this.picUrl = base64 // 存储地址
      this.picKey = key // 存储唯一标识
    }
  }

}
</script>

<style lang="less" scoped>
...
</style>
2. 代码示例

四. 封装图片验证码接口

目标:将请求封装成方法,统一存放到 api 模块,与页面分离

以前的模式:

        (1).页面中充斥着请求代码,可阅读性不高

        (2).相同的请求没有复用

        (3).请求没有统一管理


封装api模块的好处:

        (1). 请求与页面逻辑分离

        (2).相同的请求可以直接复用

        (3).请求进行了统一管理

整体步骤:

1. 封装请求函数
// src/api/login.js

// 此处用于存放所有登录相关的接口请求
import request from '@/utils/request'
// 1. 获取图形验证码
// 按需导出
export const getPicCode = () => {
  return request.get('/captcha/image') // 接口地址
}
2. 页面导入调用
// src/views/login/index.vue

<template>
 ...
</template>
<script>
// 按需导入
import { getPicCode } from '@/api/login'
export default {
  name: 'LoginIndex',
  data () {
    return {
     ...
    }
  },
  created () {
    ... 
  },
  methods: {
    // 发送请求封装为一个方法
    async getPicCode () {
      const { data: { base64, key } } = await getPicCode()// 调用封装方法
      this.picKey = key // 存储唯一标识
    }
  }
}
</script>

<style lang="less" scoped>
...
</style>
3. 代码示例

五. toast轻提示

目标:阅读vant文档,掌握toast 轻提示

注册安装:

import { Toast } from 'vant'
Vue.use(Toast)
1. 两种使用方式

        (1). 导入使用 (组件内 或 非组件中均可)

import { Toast } from 'vant'
Toast('提示内容')

        (2). 通过this直接调用(必须组件内)

                本质: 将方法,注册挂载到了Vue原型上 Vue.prototype.$toast=xxx

this.$toast('提示内容')
    2. 代码示例

        (1). 导入使用

        (2). 通过this直接调用

 

六. 短信验证功能

目标: 实现短信验证倒计时功能

步骤分析:

        ①.点击按钮,实现 倒计时 效果

        ②.倒计时之前的 校验处理(手机号、验证码)

        ③.封装短信验证请求接口,发送请求添加提示

1. 倒计时基础效果
        (1). 准备data数据
data () {
    return {
      totalSecond: 60, // 总秒数
      second: 60, // 当前秒数,开定时器对 second--
      timer: null // 定时器 id
    }
  }
        (2). 给按钮注册点击事件
<button @click="getCode">
   {{ second === totalSecond  ? '获取验证码' : second + '秒后重新发送'}}
</button>
        (3). 开启倒计时
 // 获取短信验证码
    getCode () {
      // 当目前没有定时器开着,且 totalSecond 和 second 一致(秒数归位) 才可以倒计时
      if (!this.timer && this.second === this.totalSecond) {
        // 开启倒计时,每隔一秒减1
        this.timer = setInterval(() => {
          this.second--

          if (this.second <= 0) {
            clearInterval(this.timer) // 停止定时器
            this.timer = null // 重置定时器 id
            this.second = this.totalSecond // 归位
          }
        }, 1000)
        // 发送请求,获取验证码
        this.$toast('发送成功,请注意查收')
      }
    }
                (4). 离开页面销毁定时器
// 离开页面清除定时器
  destroyed () {
    clearInterval(this.timer)
  }
}
                (5). 代码示例

2. 验证码请求校验处理
        (1). 输入框v-model绑定变量
 data () {
    return {
      mobile: '', // 手机号
      picCode: '' // 用户输入的图形验证码
    }
  },


<input class="inp" v-model='mobile' maxlength="11" placeholder="请输入手机号" type="text">
<input v-model="picCode" class="inp" maxlength="5" placeholder="请输入图形验证码" type="text">
        (2). methods中封装校验方法
// 校验 手机号 和 图形验证码是否合法
    validFn () {
      if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
        this.$toast('请输入正确的手机号')
        return false
      }
      if (!/^\w{4}$/.test(this.picCode)) {
        this.$toast('请输入正确的图形验证码')
        return false
      }
      return true
    },
        (3). 请求倒计时前 进行校验
// 获取短信验证码
    getCode () {
      if (!this.validFn()) {
        // 如果没通过校验,没必要往下走
        return
      }
      ...
}
        (4). 代码示例

3. 封装接口,请求获取验证码
        (1). 封装接口 api / login.js
// 获取短信验证码
export const getMsgCode = (captchaCode, captchaKey, mobile) => {
  return request.post('/captcha/sendSmsCaptcha', {
    form: {
      captchaCode,
      captchaKey,
      mobile
    }
  })
}
        (2). 调用接口,添加提示
// 获取短信验证码
    async getCode () {
      if (!this.validFn()) {
        // 如果没通过校验,没必要往下走
        return
      }

      // 当目前没有定时器开着,且 totalSecond 和 second 一致(秒数归位) 才可以倒计时
      if (!this.timer && this.second === this.totalSecond) {
        // 发送请求
        await getMsgCode(this.picCode, this.picKey, this.mobile)
        this.$toast('短信发送成功,请注意查收')
    
        // 开启倒计时
        ...
}
            (3). 代码示例

七. 登录功能

目标:封装api登录接口,实现登录功能

步骤分析:
        ①.阅读接口文档,封装登录接口

        ②.登录前的校验(手机号,图形验证码,短信验证码)

        ③.调用方法,发送请求,成功添加提示并跳转

1. api / login.js 封装登录接口
//登录接口
export const codeLogin = (mobile, smsCode) => {
  return request.post('/passport/login', {
    form: {
      isParty: false,
      partyData: {},
      mobile,
      smsCode
    }
  })
}
2. 短信验证码绑定v-mode
 data () {
    return {
      msgCode: '' // 短信验证码
    }
  },

<input class="inp"  v-model="msgCode" placeholder="请输入短信验证码" type="text">

<div class="login-btn" @click="login">登录</div>
3. 页面中校验和调用登录接口
// 登录
    async login () {
      if (!this.validFn()) {
        return
      }

      if (!/^\d{6}$/.test(this.msgCode)) {
        this.$toast('请输入正确的手机验证码')
        return
      }
      const res = await codeLogin(this.mobile, this.msgCode)
      console.log(res)
      this.$toast('登陆成功')
      this.$router.push('/')
    }

八. 响应拦截器统一处理错误

目标:通过响应拦截器,统一处理接口的错误提示

问题:每次请求,都会有可能会错误,就都需要错误提示

说明:响应拦截器是咱们拿到数据的 第一个 数据流转站,可以在里面统一处理错误

只要不是200,就默认提示,抛出错误

        

1. 响应拦截器统一处理错误

        src /util /request.js

import axios from 'axios'
import { Toast } from 'vant'
// 创建 axios 实例,将来对创建出来的实例,进行自定义配置
// 好处: 不会污染原始的 axios 实例
...

// 自定义配置 - 请求/响应 拦截器
// 添加请求拦截器
...

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
  // 对响应数据做点什么(默认axios会多包装一层data,需要响应拦截其中处理一下)
  const res = response.data
  // 希望如果响应的status非200,最好抛出一个promise错误,await只会等待成功的promise
  if (res.status !== 200) {
    // 给提示
    Toast(res.message)

    // 控制台抛出一个错误的promise
    return Promise.reject(res.message)
  }

  return res
}, function (error) {
  // 对响应错误做点什么
 ...

// 导出配置好的实例
export default instance

九. 登录权限信息存储

目标: vuex 构建 user 模块存储登录权证(token & userld)

1. 新建vuex 的 user.js模块

        src / store / modules / user.js

export default {
  namespaced: true, // 开启命名空间
  state () {
    return {
      // 个人权证相关
      userInfo: {
        token: '',
        userId: ''
      }
    }
  },
  mutations: {}
  },
  actions: {},
  getters: {}
}
2. 挂载在vuex上

        src / store / index.js

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    user // 挂载
  }
})
    3. 提供 mutations

        src / store / modules / user.js

 mutations: {
    // 所有mutations的第一个参数都是state
    setUserInfo (state, obj) {
      state.userInfo = obj
    }
  },
        4. 页面中commit调用

                login / index.vue

 // 登录(校验 & 提交)
    async login () {
      if (!this.validFn()) {
        return
      }

      ...
      const res = await codeLogin(this.mobile, this.msgCode)
      this.$store.commit('user/setUserInfo', res.data)
      console.log(res)
      this.$toast('登陆成功')
      this.$router.push('/')
    }
        5. 代码示例

十.  storage存储模块-vuex持久化处理

目标: 封装 storage 存储模块,利用本地存储,进行 vuex 持久化处理

将token 和 userId存储到本地,刷新页面也不会丢失

1. 定义storage.js模块

        src / utils / storage.js


// 约定一个通用键名
const INFO_KEY = 'cc_shopping_info'

// 获取个人信息
export const getInfo = () => {
  const defaultObj = { token: '', userId: '' } // 默认值
  const result = localStorage.getItem(INFO_KEY)
  return result ? JSON.parse(result) : defaultObj // 判断有值就序列化
}

// 设置个人信息
export const setInfo = (obj) => {
  localStorage.setItem(INFO_KEY, JSON.stringify(obj))
}

// 移除个人信息
export const removeInfo = () => {
  localStorage.removeItem(INFO_KEY)
}
2. vuex中调用

        src / store / modules / user.js

// 导入封装的方法
import { getInfo, setInfo } from '@/utils/storage'

export default {
  namespaced: true, // 开启命名空间
  state () {
    return {
      // 个人权证相关
      userInfo: getInfo() // 直接调用get方法
    }
  },
  mutations: {
    // 所有mutations的第一个参数都是state
    setUserInfo (state, obj) {
      state.userInfo = obj
      setInfo(obj) // 调用set方法
    }
  },
  actions: {},
  getters: {}
}
3. 代码示例

十一.  添加请求loading效果

        utils / request.js 添加loading

1. 请求和响应拦截器添加loading逻辑
import axios from 'axios'
import { Toast } from 'vant'
// 创建 axios 实例,将来对创建出来的实例,进行自定义配置
...

// 自定义配置 - 请求/响应 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么

  // 开启loading,禁止背景点击(节流处理,防止多次无效点击)
  Toast.loading({
    message: '加载中...',
    forbidClick: true, // 禁止背景点击
    loadingType: 'spinner', // 配置loading图标
    duration: 0 // loading不会自动消失
  })

  return config
}...

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
  // 对响应数据做点什么(默认axios会多包装一层data,需要响应拦截其中处理一下)
  ...
  if (res.status !== 200) {
    // 给错误提示,Toast是单例模式,后面的 Toast调用了,会将前一个 Toast 效果覆盖
    // 同时只能存在一个 Toast
    ...
    // 控制台抛出一个错误的promise
    ...
  } else {
    // 正确情况,直接走业务核心逻辑,清除loading效果
    Toast.clear()
  }
  return res
}...
   2. 代码示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值