移动端新闻头条类项目的登陆功能

254 阅读6分钟

登陆需求初步分析

  1. 项目的登陆需求是要求用户通过填写手机号然后发送验证码的方式登陆
  2. 需要对输入的手机号和验证码的格式进行校验 (可能需要用到的技术点:正则表达式验证)
  3. 要考虑到用户在登陆时成功和失败的状态提示
  4. 发送验证码后需要进行倒计时,
  5. 需要做用户token值的验证 (可能需要用到的技术点:本地存储,Vuex)
  6. 最后在用户点击登陆时如果成功,要跳转页面 (可能需要用到的技术点:Router)

登陆页面的样式搭建

配置登陆页面的路由路径

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

使用vant组件库中的 NavBar导航栏组件搭建头部

  1. 需要用到该导航栏中的title属性和left-arrow属性
  • 遇到的问题:对该组件里的文字颜色或者背景颜色做修改时没有生效

  • 解决办法:先在控制台调试看能否生效,需要找到可以使其生效的类名,如果在组件的style样式上有scoped隔离属性,那么在修改样式时 需要在修改的类名前加上 /deep/这个属性,不过该属性有一些兼容问题

使用vant组件库中Form表单组件搭建主体页面

  1. 在表单中,每个Field组件代表一个表单项 ,在van-form组件中需要监听@submit时间,可以监测什么时候用户点击提交
  2. 在van-field中 需要用到 v-model属性获取输入框的值 name属性获得指定输入框里的值 rules属性用来定义正则表达式
  • 遇到的问题:在手机号的输入框里和验证码的输入框中需要插入自己定义的字体图标,不知道应该怎么插入
  • 解决办法:在van-field这个组件中是可以使用插槽的 有两种写法
  1. 第一种写法:直接给标签添加slot属性后面加上组件内定的属性名称 left-icon代表在输入框左边添加字体图标,然后写上自己定义的字体图标类名
<van-field>
 <i slot="left-icon" class="toutiao toutiao-shouji"></i>
 </van-field>
  1. 第二种写法,直接使用具名插槽包裹住需要增加的标签
<van-field>
 <template #left-icon>
  <i  class="toutiao toutiao-shouji"></i>
 </template>
 </van-field>

调用登陆接口

1.需要在api文件夹中再封装一个针对登陆注册的Login.js网络请求模块,这个接口后台要求必须传入mobile和code这两个名字的参数,一个是用户的手机号,还有一个是验证码

//引入封装的axios网络请求基础配置
import request from "@/utils/request";
// 用户登陆注册接口
export const Login = data => request({
        method: 'POST',
        url: 'v1_0/authorizations',
        data
      })
  1. mobile和code这两个数据需要从两个van-field组件中的v-model中获得,为了数据传输方便,我将这两个数据封装成了一个对象放在data中
data(){
return {
        user: {
                mobile: '18168911126',
                code: '246810',
               }
}
}

  1. 调用封装好的Login接口,在onSubmit提交事件中请求登陆(也就是用户点击登陆按钮时)在这里为了让网络请求能够作为同步任务发起请求所以使用了 async和await的技术来让网络请求变成同步任务行为(因为axios本身就是一个promise异步函数),同时使用try{}catch{}技术来捕捉程序的错误信息
<script>
//导入封装好的登陆请求模块
import { LoginApi } from '@/api'
</script>
export default {
    methods:{
    async onSubmit(values) {
    //定义一个变量接收登陆请求需要传递的参数
    const user = this.user
   try{
    //调用登陆接口,得到返回的对象
    const {data} = await LoginApi(user)
     
   }catch(err){
   
   }
   
}
    }
}

登陆时的表单验证

登陆状态提示

  1. 需要用到vant组件库中的Toast提示组件
  • 遇到的问题:因为本项目使用的是全局注册,所以直接引用Toast提示组件的一些代码是无法生效的,会显示Toast未被定义
  • 解决方法:需要使用this.toast的方式来调用,即可生效,其中的this.toast的方式来调用,即可生效,其中的this.toast.loading , this.toast.successthis.toast.success,this.toast.fail 是Toast提示组件中自带的属性,详情可以参考vant2组件库里的Toast提示组件
<script>
//导入封装好的登陆请求模块
import { LoginApi } from '@/api'
</script>
export default {
    methods:{
   async onSubmit(values) {
            const user = this.user
            // 引入vant组件库的Toast组件来完成提示信息 
            this.$toast.loading({
                message: '加载中...',
                forbidClick: true, //是否禁止背景点击
                duration: 0 //展示时间
            });
            try {
                const {data} = await LoginApi(user)
                console.log(data);
                this.$toast.success('登陆成功')
            } catch (err) {
                if (err.response.status == 400) {
                    this.$toast.fail('手机号或验证码错误')
                } else {
                    this.$toast.fail('登陆失败');
                }
            }
        },
   
}
    }
}

登陆表单验证

  1. 使用vant组件库中的Form表单组件完成验证,利用组件中自带的rules属性完成验证
  • 属性技术点:rules属性的使用需要放在数组中,可以把需要验证的数组统一封装在一个对象中
 userFormRules: {
                mobile: [
                    { pattern: /^1[3|5|7|8]\d{9}$/, message: '手机号格式错误' },
                    { required: true, message: '手机号不能为空' }
                ],
                code: [
                    {
                        required: true,
                        message: '验证码不能为空'
                    }, {
                        pattern: /^\d{6}$/,
                        message: '验证码格式错误'
                    }
                ]
            },

发送验证码功能

  1. 使用vant组件库中的Form表单组件,其中有一个使用方法可以通过 button 插槽可以在输入框尾部插入按钮。
  • 功能需求1:需要在点击完发送验证码按钮后显示倒计时样式
  1. 使用vant组件库中的van-count-down组件,time 属性表示倒计时总时长,然后用format 属性设置倒计时文本的内容
  • 技术点:需要在发送验证码按钮和倒计时来回切换样式,需要通过v-if和v-else属性来达到切换效果,在数据中定义一个变量iscountdownShow默认设置为true,使发送验证码按钮显示,倒计时隐藏,当点击时将iscountdownShow设为false,倒计时显示,最后监听van-count-down组件的finsh事件,在倒计时结束时将iscountdownShow重新改为true,这样就能完成发送验证码按钮和倒计时的切换
 <template #button>
                    <van-button v-if="isCountDownShow" size="small" type="primary" native-type="button"
                        @click="onSendSms">发送验证码</van-button>
                    <!-- 使用countdown倒计时组件 -->
                    <!-- 定义了一个isCountDownShow变量控制发送验证码按钮和倒计时的状态切换 -->
                    <van-count-down @finish="isCountDownShow = true" :time="time" format="ss 秒" v-else />
                </template>
  1. 在api文件夹中封装发送短信的网络请求,接口定义参数mobile(手机号)需要拼接在路径后面传递
// 发送验证码接口
export const getSmsCode = mobile => {
  return request({
    method: 'GET',
    url: `v1_0/sms/codes/${mobile}`
  })
}
  1. 将接口导入login.vue中,给发送验证码按钮添加点击事件,在点击事件中发送验证码网络请求
  • 需求技术点:这里需要在发起验证码网络请求时对手机号再一次的做校验,这里还是通过Form组件的一些内置方法来完成,给手机号的输入框设置了一个name属性,然后使用ref方法绑定From表单,在点击事件中需要form组件原生dom调用validate()方法,参数需要填入name属性,这样就可以对对应name属性的输入框完成校验
 // 发送验证码
        async onSendSms() {
            // 1:校验手机号
            // try catch 只能捕捉同步代码的正确和错误
            try {
                // 需要form组件原生dom调用validate()方法,参数需要填入name属性
                await this.$refs.loginForm.validate('mobile')

            } catch (err) {
                return console.log('验证失败', err);
            }
            // 2:验证通过,显示倒计时
            this.isCountDownShow = false
            // 3:请求发送验证码
            try {
                await getSmsCode(this.user.mobile)
                this.$toast.success('发送成功');
            } catch (err) {
                // 发送失败关闭定时器
                this.isCountDownShow = false
                if (err.response.status === 429) {
                    this.$toast('发送太频繁了,请稍后重试')
                } else {
                    this.$toast('发送失败,请稍后重试')
                }
            }
        }

如何存储用户token

  1. 在登陆成功之后,后台会返回一个对象,里面会存储用户的两个token值,这两个值分别用来实现用户身份验证(token)和无感登陆(refresh_token)

WechatIMG522.jpeg

  • 需求技术点:需要利用vuex技术,把用户的token储存起来,这样方便调用,在vuex的state属性中定义一个变量user为null,mutations属性中定义一个方法setUser,在登陆成功后,使用this.store.commit()token值传递到setUser方法中对state中的user属性进行修改,哪里需要使用token只需要通过this.store.commit()将token值传递到setUser方法中对state中的user属性进行修改,哪里需要使用token只需要通过this.store.state.user的方式或者 vuex中的mapstae方法, ...mapState(['user']) 取值即可

  • 遇到的问题:当我在使用vuex技术存值的时候虽然储存成功,可是当我进行刷新时,储存的token值就没有了

  • 解决方法:使用vuex的存储技术和本地存储技术结合使用,存值和取值都从本地中获取,就可以实现永久存储

  • 本地存储遇到的问题:有时候会出现数据取不出来或者存不进去的情况

  • 解决方法:在使用本地存储时会需要数据在json类型和object数据类型之间来回切换,所以在进行本地存储时需要对数据去做一些判断,这里我选择把本地存储封装起来,然后统一导入到vuex中调用

  • 本地存储封装的代码

// 获取本地存储到函数
export const getItem = (name) => {
    const data = localStorage.getItem(name)
    //   为了防止用户输入的本地存储名称错误导致取不出值 用try catch包裹
    try {
        return JSON.parse(data)
    } catch (err) {
        return data 
    }
}
// day02 设置本地存储函数
export const setItem = (name, val) => {
    // 判断存入的值是否为null
    if ((val + '') == 'null') {
        return 
    }
    // 判断数据类型是否为对象 如果是则转
    if (typeof val == 'object') {
        val = JSON.stringify(val)
    }
    // 本地存储
    const data = localStorage.setItem(name, val)
}
//   删除本地存储
export const removeItem = name => {
    window.localStorage.removeItem(name)
  }

  • vuex的代码
import Vue from 'vue'
import Vuex from 'vuex'
// 引入封装好的函数
import { setItem, getItem } from '@/utils/storage'
Vue.use(Vuex)
// day02 定义一个常量存储本地存储名字
const TOKEN_KEY = 'toutiao_user'
export default new Vuex.Store({
  state: {
    // 一个对象 存储当前登陆用户信息(token)
    // JSON.parse(window.localStorage.getItem(TOKEN_KEY))
    user: getItem(TOKEN_KEY)
  },
  getters: {
  },
  mutations: {
    setUser(state, data) {
      state.user = data
      //day02 把数据存储到本地
      // window.localStorage.setItem(TOKEN_KEY, JSON.stringify(state.user))
      setItem( TOKEN_KEY,state.user)
    },
    
  },
  actions: {
  },
  modules: {
  }
})

利用axios请求拦截器继续优化对token的处理

  • 这里我判断了user和user.token两项数据,防止后台传入为null导致未知错误
// 请求拦截器技术 *
// Add a request interceptor
request.interceptors.request.use(function (config) {
    // 请求发起会经过这里
    // config :本次请求的配置对象
    // config 里面有一个属性:headers
    const { user } = store.state
    if (user && user.token) {
      config.headers.Authorization = `Bearer ${user.token}`
    }
    console.log(config);
    // 必须返回一个新的config配置对象,否则请求停在这里出不去
    return config
  }, function (error) {
    // Do something with request error
    // 因为发起请求都是使用async await修饰,所以不管成功或失败都必须返回promise函数
    return Promise.reject(error)
  })