文章目录
- 一、注册
- 1. 获取注册验证码
- 2. 完成注册用户
- 二、登录
- 1. 登录获取token
- 2. Home首页携带token获取用户数据
- 3. 持久化存储token
- 4. 退出登录
- 5. 导航守卫 (牛)
- 6. 路由独享守卫beforeEnter
- 7. 组件内守卫(用的很少)
一、注册
1. 获取注册验证码
本系统:点击按钮,发送请求,后台返回生成的验证码。(正常的业务逻辑是:点击按钮,后台根据手机号给手机发送验证码,用户输入验证码。)
javascript">1. 接口
// 获取验证码// /api/user/passport/sendCode/{phone}
export const reqRegisterCode = function (phone) {return requests.get(`/user/passport/sendCode/${phone}`);
}
javascript">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: {},
}
javascript">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. 完成注册用户
拿着手机号、密码、验证码去发请求,将该用户添加到数据库中。
javascript">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}})
}
javascript">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))}}
}
注册成功则跳转到登录页面
javascript">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
javascript">1. 登录接口
// 登录 /api/user/passport/login 请求:方法post
export const reqUserLogin = (phone, password) => {return requests({url: '/user/passport/login',method: 'post',data: {phone,password}})
}
javascript">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并非持久化存储。
javascript">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) 获取用户信息的接口
javascript">// 获取用户信息 /api/user/passport/auth/getUserInfo
export const reqUSerInfo = () => {return requests({url: '/user/passport/auth/getUserInfo',method: 'get',})
}
(2). vuex三连环存储用户信息
javascript">state: {code: '',token: '',userInfo: {}
},
actions:{// 获取用户信息async getUserInfo (context) {let res = await reqUSerInfo()}
},
mutations:{GETUSERINFO (state, data) {state.userInfo = data}
}
(3). 请求拦截器携带token
javascript">// 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组件一挂载完毕就发送获取用户信息的请求。
javascript">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>javascript">computed: {userName () {return this.$store.state.user.userInfo.name}
}
</script>
3. 持久化存储token
当登录成功、进入首页、获取用户信息成功渲染到界面上后。此时如果刷新页面,会发现控制台仍旧报未登录
的错误。这是因为,Vuex存储token
并不是持久化存储。刷新页面,token
数据清空,而Home页重新加载,仍旧发请求获取用户信息,没有token所以会报错。解决办法就是持久化存储token
,即本地存储。
方式一:仓库中存储、仓库中读取
请求拦截器中读取仓库里的token
,在请求头里加上token
方式二:仓库中存储,请求拦截器里读取
这里存在一个问题:首页发送获取用户信息的请求的前提是登录成功、跳转到首页。而在刚进入网站时,路由重定向的就是首页,所以刚进入网站时,就会有一个未登录的错误。(实际中,刚进入网页时不应该请求用户的信息。这个在导航守卫中被解决)
4. 退出登录
退出登录:,
(1)发请求,通知服务器退出登录
(2)清除项目当中的数据 userInfo
、token
javascript">1. 接口
// 退出登录 /api/user/passport/logout
export const reqUserLogout = () => {return requests({url: '/user/passport/logout',method: 'get',})
}
此处注意action里面不能操作state,提交mutation修改state
javascript">2. vuex
actions: {// 退出登录async LogOut (context) {let res = await reqUserLogout()if (res.code === 200) {//action里面不能操作state,提交mutation修改statecontext.commit('CLEAR')return 'ok'} else {return Promise.reject(new Error('falie'))}}
},
mutations: {CLEAR (state) {// 清空用户数据state.userInfo = {}// 仓库里的token清空state.token = ''// 本地存储清空localStorage.removeItem('TOKEN')}
}
javascript">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----比如用户点击我的订单
,但是此时未登录,则先去登录页面。登录成功之后应该直接跳转到我的订单
页面,而不是首页。
javascript">// 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)
javascript"> // 交易{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
javascript">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)。
javascript">export default {name: 'PaySuccess',beforeRouteEnter (to, from, next) {if (from.path === './pay') {next()} else {next(false)}}
}