Vue2电商平台(六)、注册登录,请求头配置token,token持久化存储;导航守卫(重点);组件内守卫、路由独享守卫

一、注册

1. 获取注册验证码

本系统:点击按钮,发送请求,后台返回生成的验证码。(正常的业务逻辑是:点击按钮,后台根据手机号给手机发送验证码,用户输入验证码。)
在这里插入图片描述

1. 接口
// 获取验证码// /api/user/passport/sendCode/{phone}
export const reqRegisterCode = function (phone) {
  return requests.get(`/user/passport/sendCode/${phone}`);
}
2. vuex小仓库
// 新建user小仓库,用来存储登录注册的信息
import { reqRegisterCode, registUser } from '@/api'
export default {
  namespaced: true,
  state: {
    code: ''
  },
  actions: {
    // 获取验证码
    async getCode (context, phone) {
      let res = await reqRegisterCode(phone)
      if (res.code === 200) {
        context.commit('GETCODE', res.data)
        return 'ok'
      } else {
        return Promise.reject(new Error('faile'))
      }
    },
  mutations: {
    GETCODE (state, data) {
      state.code = data
    }
  },
  getters: {},
}
3. Register(注册)组件内
async getRegisterCode () {
  try {
    //1. 发请求 来个逻辑短路判断手机号是否输入
    this.phone && (await this.$store.dispatch('user/getCode', this.phone))
    // 2. 获取到验证码,方式一: 
    this.code = this.$store.state.user.code
    // 方式二是用computed ...mapState('user', ['code'])
  } catch (error) {
    alert(error.message)
  }
},

v-model将验证码code双向绑定到验证码的输入框里。当后台返回验证码,组件内读取到验证码时,验证码就自动输入到input框里。

2. 完成注册用户

拿着手机号、密码、验证码去发请求,将该用户添加到数据库中。

1. 接口
// 注册用户   /api/user/passport/register  请求方法 post 
// 参数 phone:手机号,password:密码; code:验证码
export const registUser = (phone, password, code) => {
  return requests(
    {
      url: `/user/passport/register`,
      method: 'post',
      data: {
        phone, password, code
      }
    })
}
2.vuex
aciotns:{
 // 注册用户
 async sendRegister (context, { phone, password, code }) {
   let res = await registUser(phone, password, code)
   // 223 注册失败
   console.log('注册结果', res);
   if (res.code === 200) {
     return 'ok'
   } else {
   // 不同类型的失败,在res.message中都会给出解释
     return Promise.reject(new Error(res.message))
   }
 }
}

在这里插入图片描述
注册成功则跳转到登录页面

3. 组件
data () {
  return {
    phone: '',      // 电话号码
    code: '',      //验证码
    password: '',      // 登录密码
    rePassowrd: '',      // 确认密码
    isAgree: true      // 是否同意
  }
}
methods:{
 async registUser () {
  console.log('注册用户');
   try {
     const { phone, code, password, rePassowrd, isAgree } = this
     // 逻辑与验证信息
     if (phone && code && password && rePassowrd && password == rePassowrd && isAgree == true) {
       await this.$store.dispatch('user/sendRegister', { phone, password, code })
     }
     // 注册成功路由需要进行跳转,跳转到登录页面
     this.$router.push({
       path: '/login',
     })
   } catch (error) {
   // 这里的message接收的是vuex中的Promise.reject()里的值
     alert(error.message)
   }
 }
}

在这里插入图片描述
这部分还缺一个表单验证,老师用的vee-validate。ElementUI有现成的表单验证,也可以用这个。本系统就先不弄表单验证了。

二、登录

1. 登录获取token

1. 登录接口
// 登录 /api/user/passport/login  请求:方法post
export const reqUserLogin = (phone, password) => {
  return requests({
    url: '/user/passport/login',
    method: 'post',
    data: {
      phone,
      password
    }
  })
}
2. vuex存储数据
state: {
    code: '', // 验证码
    token: ''
  },
actions:{
   // 登录
  async userLogin (context, { phone, password }) {
    let res = await reqUserLogin(phone, password)
    console.log('登录结果', res);
    // 服务器下发token作为用户的唯一标识,前端经常通过携带token找服务器要一些用户的数据进行展示
    if (res.code === 200) {
      context.commit('USERLOGIN', res.data.token)
      return 'ok'
    } else {
      return Promise.reject(res.message)
    }
  }
},
 mutations: {
 USERLOGIN (state, token) {
   state.token = token
 }
}

打印请求结果。登录成功的时候,后台为了区分用户是谁,服务器会下发token作为唯一标识符。这里的登录接口做的并不好,应该只返回token。
在这里插入图片描述
需要注意:vuex存储token并非持久化存储。

4. 组件发登录请求,成功则跳转home页面(暂定是home页面)
methods: {
  async Login () {
    console.log('登录');
    try {
      await this.$store.dispatch('user/userLogin', { phone: this.phone, password: this.pwd })
      //  成功则跳转到首页【结合导航守卫,这句话后续会变】
      this.$router.push('/home')
    } catch (error) {
      alert(error.message)
    }
  }
}

一般的业务逻辑为登录成功,服务器下发token,前端持久化存储token。前端找服务器要用户数信息进行展示时需要带着token

这里存在一个问题:登录成功之后一定是跳转到首页吗?具体看导航守卫,会回来填坑的。

2. Home首页携带token获取用户数据

登录成功,进入首页时,应该获取用户信息并展示在页面上。
在这里插入图片描述
(1) 获取用户信息的接口

// 获取用户信息 /api/user/passport/auth/getUserInfo
export const reqUSerInfo = () => {
  return requests({
    url: '/user/passport/auth/getUserInfo',
    method: 'get',
  })
}

(2). vuex三连环存储用户信息

state: {
  code: '',
  token: '',
  userInfo: {}
},
actions:{
 // 获取用户信息
  async getUserInfo (context) {
    let res = await reqUSerInfo()
  }
},
mutations:{
    GETUSERINFO (state, data) {
    state.userInfo = data
  }
}
   

(3). 请求拦截器携带token

// 2. 配置请求拦截器: 请求拦截器检测到请求,在请求发出去之前做一些事情
import store from '@/store'
requests.interceptors.request.use((config) => {
  // 进度条开始
  nProgress.start()
  // config:配置对象,其中header请求头属性很重要
  config.headers.userTempId = store.state.detail.userTempId
  // 配置token请求头
  if (store.state.user.token) {
    config.headers.token = store.state.user.token
  }
  return config;
})

Home组件一挂载完毕就发送获取用户信息的请求。

4. Home组件发请求
mounted () {
  // 发送请求,获取用户信息,【结合导航守卫,这格请求以后不在这里执行】
  this.$store.dispatch('user/getUserInfo')
},

在这里插入图片描述
如果不携带token,发送获取用户信息的请求时,服务器的返回结果是 未登录
在这里插入图片描述
(5). 将用户信息渲染到界面上

 <!-- 登录成功 则显示用户信息-->
 <p v-show="userName">
   <span>{{ userName }}</span>
   <router-link to="/login" class="register">退出登录</router-link>
 </p>
 <!-- 未登录则显示请登录等信息 -->
 <p v-show="!userName">
   <span></span>
   <router-link to="/login">登录</router-link>
   <router-link to="/register" class="register"
     >免费注册</router-link   >
 </p>
<script>
 computed: {
  userName () {
    return this.$store.state.user.userInfo.name
  }
}
</script>

在这里插入图片描述

3. 持久化存储token

当登录成功、进入首页、获取用户信息成功渲染到界面上后。此时如果刷新页面,会发现控制台仍旧报未登录的错误。这是因为,Vuex存储token并不是持久化存储。刷新页面,token数据清空,而Home页重新加载,仍旧发请求获取用户信息,没有token所以会报错。解决办法就是持久化存储token,即本地存储。

方式一:仓库中存储、仓库中读取
在这里插入图片描述
请求拦截器中读取仓库里的token,在请求头里加上token
在这里插入图片描述
方式二:仓库中存储,请求拦截器里读取
在这里插入图片描述
在这里插入图片描述
这里存在一个问题:首页发送获取用户信息的请求的前提是登录成功、跳转到首页。而在刚进入网站时,路由重定向的就是首页,所以刚进入网站时,就会有一个未登录的错误。(实际中,刚进入网页时不应该请求用户的信息。这个在导航守卫中被解决)

4. 退出登录

退出登录:,
(1)发请求,通知服务器退出登录
(2)清除项目当中的数据 userInfotoken

1. 接口
// 退出登录 /api/user/passport/logout
export const reqUserLogout = () => {
  return requests({
    url: '/user/passport/logout',
    method: 'get',
  })
}

此处注意action里面不能操作state,提交mutation修改state

2. vuex
actions: {
  // 退出登录
  async LogOut (context) {
    let res = await reqUserLogout()
    if (res.code === 200) {
     //action里面不能操作state,提交mutation修改state
      context.commit('CLEAR')
      return 'ok'
    } else {
      return Promise.reject(new Error('falie'))
    }
  }
},
mutations: {
  CLEAR (state) {
    // 清空用户数据
    state.userInfo = {}
    // 仓库里的token清空
    state.token = ''
    // 本地存储清空
    localStorage.removeItem('TOKEN')
  }
}
3. Header 组件内
 // 退出登录
 async userLogOut () {
   try {
     // 请求成功,则跳转页面
     await this.$store.dispatch('user/LogOut')
     // 回到首页
     this.$router.push('/login')
   } catch (error) {
     alert(error.message)
   }
 }

这里跳回首页会有一个bug

5. 导航守卫 (牛)

路由守卫的具体内容回顾这篇博客:Vue(十三) 路由守卫
导航:表示路由正在发生变化。导航守卫就是在进行路由跳转时进行的一些操作。

之前的程序存在的问题:
Q1: 用户在登录之后,地址栏中输入/login,仍旧能够进入登录页面。
Q2: 在刚进入网站时,重定向到首页,首页会请求用户信息,若此时还未登录,则没有token,请求会报错。
Q3: 由于获取用户信息的请求是在Home模块。假如进入search页面后,刷新页面,虽然有token,但是Search模块并不会发送请求,所以页面的用户信息还是会丢失。
Q4: 登录成功就一定跳转Home首页吗?

这些问题都是在路由变化的情况下发生的。导航守卫:

在这里插入图片描述
Q2,Q3----请求放在导航守卫里,不放在首页了。无论跳转哪个页面,即使是Search页面,如果没有用户信息,就会发起请求,获取数据并放到仓库重。Header组件再去读取仓库里存储的用户信息。

Q4----比如用户点击我的订单,但是此时未登录,则先去登录页面。登录成功之后应该直接跳转到我的订单页面,而不是首页。

// router/index.js
router.beforeEach(async (to, from, next) => {
 // 判断是否登录
 let token = localStorage.getItem('TOKEN')
 // 1. 已登录
 if (token) {
   // 2. 已登录,又要去登录或注册页面,不让去,就待在原来的页面
   if (to.path === '/login' || to.path === '/register') {
     next(false) // 取消当前的导航,url地址会重置到from路由对应的地址
   } else {
     // 2. 已登录,不去登录或注册页面,判断是否有用户信息
     // 3. 有用户信息,放行
     if (store.state.user.userInfo.name) {
       next()
     } else {
       // 3. 没有用户信息,发请求
       try {
         // 发送请求,获取用户信息
         await store.dispatch('user/getUserInfo')
         // 获取成功,放行
         next()
       } catch (error) {
         // 获取不成功,但是又登录了,说明token失效,则退出登录,并跳转到登录页面
         await store.dispatch('user/LogOut')
         next('/login')
       }
     }
   }
 } else {
   // 未登录访问, 交易相关(trade)、支付相关(pay,paysuccess)、用户中心(center)相关跳转到 登录页面; (此处感觉用路由meta元信息也可以)
   if (to.path.indexOf('/trade') != -1 || to.path.indexOf('/pay') != -1 || to.path.indexOf('/center') != -1) {
     // 跳到登录页面去,并携带这个想去的路由地址
     next('/login?redirect=' + to.path)
   } else {
     // 去的不是上面这些路由,而是home|search|shopCart等别的路由,放行
     next()
   }
 }
})

而在登录组件中,登录成功跳转的页面为:
在这里插入图片描述

未登录,点击我的订单,跳转到登录页面,此时路由信息为:
在这里插入图片描述

6. 路由独享守卫beforeEnter

当用户登录之后,还有如下几条规则需要设置:
(1) 只有从购物车页面(shopCart)才能跳转到交易页面(创建订单)
(2) 只有从交易页面(创建订单)才能跳转到支付页面(pay)

 // 交易
  {
    name: 'trade',
    path: '/trade',
    component: Trade,
    beforeEnter (to, from, next) {
      /* 只能从购物车界面跳转到交易界面 */
      if (from.path == './shopCart') {
        next()
      } else {
        next(false)
      }
    },
  },
  // 支付
  {
    name: 'pay',
    path: '/pay',
    component: Pay,
    /* 只能从交易界面跳转到支付界面 */
    beforeEnter (to, from, next) {
      if (from.path == './trade') {
        next()
      } else {
        next(false)
      }
    }
  }

7. 组件内守卫(用的很少)

熟悉一下这几个API

export default {
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
        console.log(this) //undefined
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

beforeRouteUpdate这个API测试了一下,当路径由http://localhost:8080/#/detail/12直接变为http://localhost:8080/#/detail/14(也就是后边参数改变)时,这个API会被调用。如果由详情页(detail/12)返回商品页再进入详情页(detail/14);这个API也不会被调用。

只有从支付页面(pay)才能跳转到支付成功页面(paysuccess)。

export default {
  name: 'PaySuccess',
  beforeRouteEnter (to, from, next) {
    if (from.path === './pay') {
      next()
    } else {
      next(false)
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值