文章目录
- 一、注册
- 1. 获取注册验证码
- 2. 完成注册用户
- 二、登录
- 1. 登录获取token
- 2. Home首页携带token获取用户数据
- 3. 持久化存储token
- 4. 退出登录
- 5. 导航守卫 (牛)
- 6. 路由独享守卫beforeEnter
- 7. 组件内守卫(用的很少)
一、注册
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)清除项目当中的数据 userInfo
、token
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)
}
}
}