使用Vue开发项目(黑马头条项目)--第二天(下)

575 阅读3分钟

需要实现的主要功能如下:

资讯列表、标签页切换,文章举报,频道管理、文章详情、阅读记忆,关注功能、点赞功能、评论功能、回复评论、搜索功能、登录功能、个人中心、编辑资料、小智同学 ...

上面实现了登录的功能,下面我们具体的来实现对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并复制

image.png 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值

image.png

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的调试工具中看到如下效果说明成功

image.png

今天主要实现的功能到此为止!

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('登录失败')
      }
    },

5.5 最终实现功能如下:

image.png