需要实现的主要功能如下:
资讯列表、标签页切换,文章举报,频道管理、文章详情、阅读记忆,关注功能、点赞功能、评论功能、回复评论、搜索功能、登录功能、个人中心、编辑资料、小智同学 ...
上面实现了登录的功能,下面我们具体的来实现对token的处理
1 Token的基本使用流程
1)用户登录,获取token;
2)保存token到本地;
3)发起其它请求,携带token(不带token的访问会导致401错误)
1.1 在api/user.js中,根据接口文档的要求,添加一个getInfo功能,用来它去调用接口
/**
* 获取用户个人资料
*/
export const getProfile = () => {
return request({
method: 'GET',
url: 'v1_0/user/profile'
})
}
1.2 在src/views/login/login.vue这个组件去调用getInfo功能,来取用户的信息
import {getProfile } from '@/api/user'
async doLogin () {
// 尝试去调用一下获取用户的个人资料
getProfile()
// ....
}
现在测试获取用户信息会报错,因为传递过去的数据中没有token
1.3我们手动加入token来测试获取,在之前登录返回的数据中找到token并复制
在user/profile
中加入自己复制的token格式参考如下
/**
* 获取用户个人资料
*/
export const getProfile = () => {
return ajax({
method: 'GET',
url: '/v1_0/user/profile',
headers: {
// Authorization: 'Bearer token值' 之间有空格
Authorization: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTM4NTIyMjgsInVzZXJfaWQiOjExMDI0OTA1MjI4Mjk3MTc1MDQsInJlZnJlc2giOmZhbHNlfQ.gR880MifO8GIFG6PNh9eOZGGpfcwNRkK6MpI1upN93w'
}
})
}
1.4 测试后 我们发现加上token的请求可以正常使用了
token是一个特殊的字符串,在登录成功之后由后端返回。对于一些需要权限的接口,我们要根据接口文档的要求,在调用接口时,补充token。
2 token --- 把token保存在vuex
2.1 在 store/index.js
中: 补充actions, mutations, state这三个部分
import {login} from '@/api/user.js'
// 公共数据
state: {
tokenInfo: {}
},
// 定义mutation来修改数据
mutations: {
mSetTokenInfo (state, initTokenInfo) {
state.tokenInfo = initTokenInfo
}
},
actions: {
async userLogin (context, userInfo) {
try {
//第②
const { data: { data } } = await login(userInfo)
//第③
context.commit('mSetTokenInfo', data)
} catch (err) {
// console.log(err)
throw new Error(err)
}
}
},
若此处不写throw new Error(err)将错误抛出,会导致代码出现 执行顺序混乱问题:未获取到数据就显示登录成功
此处解决方法出了throw new Error(err)将错误抛出之外,还可以不写try catch;我们默认用第一种方法
2.2 调用action保存token信息
在login/login.vue中,修改doLogin的代码:验证成功通过校验时,提交actions
async doLogin () {
try {
this.$toast.loading({
duration: 0, // 持续展示 toast,永远不会关闭
overlay: true, // 整体添加一个遮罩
message: '登录中...'
})
//第①
await this.$store.dispatch('userLogin', this.user)
//第④
this.$toast.success('登录成功')
} catch (err) {
console.log(err)
this.$toast.fail('登录不成功')
}
}
上述两步代码的正确执行顺序为①=>④
上述不写throw new Error(err)的顺序为①、②、④、③ 错误没有抛出反而被自己的catch执行,导致后续代码认为没有错误,最终执行了登录成功
2.3 观察vuex定义的tokenInfo是否存进去了token值
3 axios-请求拦截器
3.1 拦截器的格式
// 请求拦截器
instance.interceptors.request.use(function (config) {
console.log('config', config)
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
3.2 在axios请求拦截器中添加token
在src\utils\request.js
中加入如下代码
// 请求拦截器
request.interceptors.request.use(function (config) {
// 如何取出vuex中保存的数据?
// 1. vue组件中: this.$store.state.tokenInfo.token
// 2. 在普通的.js文件中: import store from '@/store/index.js'
console.log('如何取出vuex中保存的数据?', store)
const token = store.state.tokenInfo.token
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
console.log('config请求拦截器2', config)
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
4 保存用户信息到vuex
由于用户信息也需要在不同的组件之间共享,所以也保存在vuex中 ,
4.1 添加一个模块store/modules/user.js
并设置 namespaced为true
import { getProfile } from '@/api/user.js'
export default {
namespaced: true,
state: {
userInfo: {}
},
mutations: {
mSetUserInfo (state, newUserInfo) {
state.userInfo = newUserInfo
}
},
actions: {
async getProfile (context) {
try {
const res = await getProfile()
console.log('modules, getProfile', res)
context.commit('mSetUserInfo', res.data.data)
} catch (err) {
console.log(err)
throw new Error(err)
}
}
}
}
4.2 在主模块store/index.js
中引用
// 省略其他...
import user from './modules/user.js'
export default new Vuex.Store({
// 省略其他...
modules: {
user
}
})
4.3 在login.vue中调用action
async doLogin () {
this.$toast({
duration: 0, // 持续展示 toast,永远不会关闭
overlay: true, // 整体添加一个遮罩
message: '加载中...'
})
// 做具体的ajax提交动作
try {
// 组件中调用action
console.log(1)
// 获取token 并保存在vuex中
await this.$store.dispatch('userLogin', this.user)
// 调用带命名空间的moudules中的action
+++++ await this.$store.dispatch('user/getProfile')
console.log(4)
this.$toast.success('登录成功')
} catch (err) {
console.log(err)
this.$toast.fail('登录失败')
}
},
4.4 在vuex的调试工具中看到如下效果说明成功
今天主要实现的功能到此为止!
5接下来组建一下layout框架为明天项目做准备
5.1 建立文件: src/view/layout/layout.vue, 内容如下:
<template>
<div class="container">
<!-- 1. 顶部导航 -->
<div>
logo + 按钮
</div>
<!-- 2. 主体区域
将来会装入四个子页面
-->
<!-- <router-view></router-view> -->
<!-- 3. 底部的tabbar -->
<div>
底部的tabbar-4个导航按钮
</div>
</div>
</template>
5.2 路由配置
在src/router/index.js中补充一个路由配置
{
path: '/',
name: 'layout',
component: () => import('../views/layout/layout.vue')
},
5.3 在src/view/layout/layout.vue
中补全代码:
<template>
<div class="container">
<!-- 顶部导航 -->
<!-- logo导航 logo + 按钮 -->
<!-- 头部:logo+搜索 -->
<van-nav-bar>
<!-- <template #left>
#left就是slot="left"的简写,具名插槽
template:是逻辑上的容器,它不会生成对应的dom
-->
<template slot="left">
<div class="logo"></div>
</template>
<template slot="right">
<van-button round icon="search" size="small" type="primary"
>搜索</van-button
>
</template>
</van-nav-bar>
<!-- 主体区域 -->
<router-view></router-view>
<!-- 底部的tabbar -->
<van-tabbar route>
<van-tabbar-item icon="home-o" to="/">主页</van-tabbar-item>
<van-tabbar-item icon="question-o" to="/question">问答</van-tabbar-item>
<van-tabbar-item icon="video-o" to="/video">视频</van-tabbar-item>
<van-tabbar-item icon="setting-o" to="/setting"
>未登陆|我的</van-tabbar-item
>
</van-tabbar>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.logo {
background: url("../../assets/logo.png") no-repeat;
background-size: cover;
width: 100px;
height: 30px;
}
.van-nav-bar .van-icon {
color: #fff;
}
</style>
- #left 是 slot="left"的简写
- van-tabber的route属性可以开启路由模式,通过to来指定要跳转的路由地址。
5.4实现登录跳转到首页layout 小功能
在
src/views/login/login.vue
中加入一行代码:
async doLogin () {
this.$toast({
message: '加载中...',
forbidClick: true,
loadingType: 'spinner'
})
// 做具体的提交动作
try {
// 由于actions中有异步的操作,这里补充await
// const { data: { data } } = await login(this.user)
// console.log(data)
// localStorage.setItem('token', 'Bearer ' + data.token)
// console.log(1)
// 获取token并保存在vuex中
await this.$store.dispatch('setToken', this.user)
// 调用命名空间的modules存储用户信息
await this.$store.dispatch('user/getProfile')
// console.log(4)
this.$toast.success('登录成功')
// 页面跳转
+++++ this.$router.push('/')
} catch (err) {
console.log(err)
this.$toast.fail('登录失败')
}
},