# 三、个人中心

# 1 模块介绍

# 2 TabBar 处理

通过分析页面,我们可以看到,首页、问答、视频、我的 都使用的是同一个底部标签栏,我们没必要在每个页面中都写一个,所以为了通用方便,我们可以使用 Vue Router 的嵌套路由来处理。

  • 父路由:一个空页面,包含一个 tabbar,中间留子路由出口
  • 子路由
    • 首页

    • 问答

    • 视频

    • 我的

# 2.1 创建layout并配置路由

image-20200109153050432

这里主要使用到的 Vant 组件:

1、创建 src/views/layout/index.vue

<template>
  <div class="layout-container">
    <!-- 子路由出口 -->
    <router-view />
    <!-- /子路由出口 -->

    <!-- 标签导航栏 -->
    <!--
      route: 开启路由模式
     -->
    <van-tabbar class="layout-tabbar" route>
      <van-tabbar-item to="/">
        <i slot="icon" class="toutiao toutiao-shouye"></i>
        <span class="text">首页</span>
      </van-tabbar-item>
      <van-tabbar-item to="/qa">
        <i slot="icon" class="toutiao toutiao-wenda"></i>
        <span class="text">问答</span>
      </van-tabbar-item>
      <van-tabbar-item to="/video">
        <i slot="icon" class="toutiao toutiao-shipin"></i>
        <span class="text">视频</span>
      </van-tabbar-item>
      <van-tabbar-item to="/my">
        <i slot="icon" class="toutiao toutiao-wode"></i>
        <span class="text">我的</span>
      </van-tabbar-item>
    </van-tabbar>
    <!-- /标签导航栏 -->
  </div>
</template>

<script>
export default {
  name: 'LayoutIndex',
  components: {},
  props: {},
  data () {
    return {
    }
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>

<style scoped lang="less">
.layout-container {
  .layout-tabbar {
    i.toutiao {
      font-size: 40px;
    }
    span.text {
      font-size: 20px;
    }
  }
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

2、然后将 layout 组件配置到一级路由

{
  path: '/',
  component: () => import('@/views/layout')
}
1
2
3
4

访问 / 测试。

# 2.2 创建主界面

1、分别创建首页、问答、视频、我的页面组件

首页组件:views/home/index.vue

<template>
  <div class="home-container">首页</div>
</template>

<script>
export default {
  name: 'Home',
  components: {},
  props: {},
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>
<style scoped lang="less"></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

问答组件:views/qa/index.vue

<template>
  <div class="qa-container">问答</div>
</template>
<script>
export default {
  name: 'Qa',
  components: {},
  props: {},
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>
<style scoped lang="less"></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

视频组件:views/video/index.vue

<template>
  <div class="video-container">首页</div>
</template>

<script>
export default {
  name: 'Video',
  components: {},
  props: {},
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>
<style scoped lang="less"></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

我的组件:views/my/index.vue

<template>
  <div class="my-container">首页</div>
</template>

<script>
export default {
  name: 'MyPage',
  components: {},
  props: {},
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>
<style scoped lang="less"></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

2、将四个主页面配置为 layout 的子路由

{
  path: '/',
  name: 'layout',
  component: () => import('@/views/layout'),
  children: [
    {
      path: '', // 默认子路由
      name: 'home',
      component: () => import('@/views/home')
    },
    {
      path: '/qa',
      name: 'qa',
      component: () => import('@/views/qa')
    },
    {
      path: '/video',
      name: 'video',
      component: () => import('@/views/video')
    },
    {
      path: '/my',
      name: 'my',
      component: () => import('@/views/my')
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

最后测试。

# 3 my页面布局

# 3.1 未登录头部状态

<!-- 未登录 -->
<div class="header not-loign">
 <div class="login-btn" @click="$router.push('/login')">
    <img class="mobile-img" src="@/assets/mobile.png">
    <span class="text">登录&nbsp;/&nbsp;注册</span>
 </div>
</div>
1
2
3
4
5
6
7

样式

.my-container {
  .header {
    height: 361px;
    background: url("~@/assets/banner.png");
    background-size: cover;
  }
  .not-login {
    display: flex;
    justify-content: center;
    align-items: center;
    .login-btn {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      .mobile-img {
        width: 132px;
        height: 132px;
        margin-bottom: 15px;
      }
      .text {
        font-size: 28px;
        color: #fff;
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 3.2 已登录头部

<!-- 已登录 -->
<div class="header user-info">
  <!-- 用户信息 -->
  <div class="base-info">
    <div class="left">
      <van-image class="avatar"
                 round
                 fit="cover"
                 src="https://img.yzcdn.cn/vant/cat.jpeg"
                 />
      <span class="name">黑马1号帅哥</span>
    </div>
    <div class="right">
       <van-button size="mini" round>编辑资料</van-button>
    </div>
  </div>
  <!-- 用户数据 -->
  <div class="data-stats">
    <div class="data-item">
      <span class="count">10</span>
      <span class="text">头条</span>
    </div>
    <div class="data-item">
      <span class="count">10</span>
      <span class="text">关注</span>
    </div>
    <div class="data-item">
      <span class="count">10</span>
      <span class="text">粉丝</span>
    </div>
    <div class="data-item">
      <span class="count">10</span>
      <span class="text">粉丝</span>
    </div>
  </div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

样式

.my-container {
  /*上面是其他样式*/
  .user-info {
    .base-info {
      height: 231px;
      padding: 76px 32px 23px;
      box-sizing: border-box;
      display: flex;
      justify-content: space-between;
      align-items: center;
      .left {
        display: flex;
        align-items: center;
        .avatar {
          width: 132px;
          height: 132px;
          border: 4px solid #fff;
          margin-right: 23px;
        }
        .name {
          font-size: 30px;
          color: #fff;
        }
      }
    }
    .data-stats {
      display: flex;
      .data-item {
        height: 130px;
        flex: 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        color: #fff;
        .count {
          font-size: 36px;
        }
        .text {
          font-size: 23px;
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 3.3 宫格导航

<!-- 宫格导航 -->
<van-grid class="grid-nav mb-9" :column-num="2" clickable>
    <van-grid-item class="grid-item">
        <i slot="icon" class="toutiao toutiao-shoucang"></i>
        <span slot="text" class="text">收藏</span>
    </van-grid-item>
    <van-grid-item class="grid-item">
        <i slot="icon" class="toutiao toutiao-lishi"></i>
        <span slot="text" class="text">历史</span>
    </van-grid-item>
</van-grid>
<!-- /宫格导航 -->
1
2
3
4
5
6
7
8
9
10
11
12

样式

.my-container {
    /*上面是其他样式*/
  .grid-nav {
    .grid-item {
      height: 141px;
      i.toutiao {
        font-size: 45px;
      }
      .toutiao-shoucang {
        color: #eb5253;
      }
      .toutiao-lishi {
        color: #ff9d1d;
      }
      span.text {
        font-size: 28px;
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 3.4 单元格导航

<!-- 单元格导航 -->
<van-cell title="消息通知" is-link />
<van-cell class="mb-9" title="小智同学" is-link />
<van-cell  class="logout-cell"
          clickable
          title="退出登录"
          />
1
2
3
4
5
6
7

样式

.logout-cell {
    text-align: center;
    color: #d86262;
}
.mb-9 {
	margin-bottom: 9px;
}
1
2
3
4
5
6
7

布局完成后的所有代码

<template>
  <div class="my-container">
    <!-- 未登录 -->
    <div class="header not-login">
     <div class="login-btn" @click="$router.push('/login')">
        <img class="mobile-img" src="@/assets/mobile.png">
        <span class="text">登录&nbsp;/&nbsp;注册</span>
     </div>
    </div>
    <!-- 已登录 -->
    <div class="header user-info">
      <!-- 用户信息 -->
      <div class="base-info">
        <div class="left">
          <van-image class="avatar"
                     round
                     fit="cover"
                     src="https://img.yzcdn.cn/vant/cat.jpeg"
                     />
          <span class="name">黑马1号帅哥</span>
        </div>
        <div class="right">
           <van-button size="mini" round>编辑资料</van-button>
        </div>
      </div>
      <!-- 用户数据 -->
      <div class="data-stats">
        <div class="data-item">
          <span class="count">10</span>
          <span class="text">头条</span>
        </div>
        <div class="data-item">
          <span class="count">10</span>
          <span class="text">关注</span>
        </div>
        <div class="data-item">
          <span class="count">10</span>
          <span class="text">粉丝</span>
        </div>
        <div class="data-item">
          <span class="count">10</span>
          <span class="text">粉丝</span>
        </div>
      </div>
    </div>
    <!-- 宫格导航 -->
    <van-grid class="grid-nav mb-9" :column-num="2" clickable>
       <van-grid-item class="grid-item">
           <i slot="icon" class="toutiao toutiao-shoucang"></i>
           <span slot="text" class="text">收藏</span>
        </van-grid-item>
        <van-grid-item class="grid-item">
            <i slot="icon" class="toutiao toutiao-lishi"></i>
            <span slot="text" class="text">历史</span>
        </van-grid-item>
    </van-grid>
    <!-- /宫格导航 -->
    <!-- 单元格导航 -->
    <van-cell title="消息通知" is-link />
    <van-cell class="mb-9" title="小智同学" is-link />
    <van-cell  class="logout-cell"
              clickable
              title="退出登录"/>
  </div>
</template>
<script>
export default {
  name: "MyIndex",
  data() {
    return {};
  },
  created() {},
  methods: {},
  components: {},
};
</script>
<style scoped lang="less">
.my-container {
  .header {
    height: 361px;
    background: url("~@/assets/banner.png");
    background-size: cover;
  }

  .not-login {
    display: flex;
    justify-content: center;
    align-items: center;
    .login-btn {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      .mobile-img {
        width: 132px;
        height: 132px;
        margin-bottom: 15px;
      }
      .text {
        font-size: 28px;
        color: #fff;
      }
    }
  }

  .user-info {
    .base-info {
      height: 231px;
      padding: 76px 32px 23px;
      box-sizing: border-box;
      display: flex;
      justify-content: space-between;
      align-items: center;
      .left {
        display: flex;
        align-items: center;
        .avatar {
          width: 132px;
          height: 132px;
          border: 4px solid #fff;
          margin-right: 23px;
        }
        .name {
          font-size: 30px;
          color: #fff;
        }
      }
    }
    .data-stats {
      display: flex;
      .data-item {
        height: 130px;
        flex: 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        color: #fff;
        .count {
          font-size: 36px;
        }
        .text {
          font-size: 23px;
        }
      }
    }
  }

  .grid-nav {
    .grid-item {
      height: 141px;
      i.toutiao {
        font-size: 45px;
      }
      .toutiao-shoucang {
        color: #eb5253;
      }
      .toutiao-lishi {
        color: #ff9d1d;
      }
      span.text {
        font-size: 28px;
      }
    }
  }

  .logout-cell {
    text-align: center;
    color: #d86262;
  }

  .mb-9 {
    margin-bottom: 9px;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176

# 4 处理页面显示状态

我们通过仓库中的user是否存在信息来判断用户是否登录,从而决定页面呈现不同模板内容

  • 导入辅助函数

    import { mapState } from 'vuex'
    
    1
  • 将user映射为当前组件的计算属性

    computed: {
        ...mapState(['user'])
    },
    
    1
    2
    3
  • 根据user判断不同的状态

    • 未登录,展示登录按钮
    • 已登录,展示登录用户信息
<!-- 已登录头部 -->
<div v-if="user" class="header user-info">   // <== 增加v-if
    ...
</div>
<!-- /已登录头部 -->

<!-- 未登录头部 -->
<div v-else class="header not-login">  // <== 增加v-else
    ....
</div>
<!-- /未登录头部 -->

<!-- 退出 -->
 <van-cell
    v-if="user"   // <== 增加v-if
    class="logout-cell"
    clickable
    title="退出登录"
/>
<!-- /退出 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 5 用户退出

思路:只需要点击的时候将仓库中的user数据删除即可

用户退出

1、给退出按钮注册点击事件

<van-cell
      v-if="user"
      class="logout-cell"
      clickable
      title="退出登录"
      @click="onLogout"   // <== 增加点击事件
    />
1
2
3
4
5
6
7

2、退出处理

onLogout () {
  // 退出提示
  // 在组件中需要使用 this.$dialog 来调用弹框组件
  this.$dialog.confirm({
    title: '确认退出吗?'
  }).then(() => {
    // on confirm
    // 确认退出:清除登录状态(容器中的 user + 本地存储中的 user)
    this.$store.commit('setUser', null)
  }).catch(() => {
    // on cancel
    console.log('取消执行这里')
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

最后测试。

# 6 展示登录用户信息

思路: 当进入个人用户中心界面之后请求用户信息

image-20200109133717775

步骤:

这是一个页面一打开请求数据的过程。我们基本可以把逻辑梳理为以步骤,可以在其他功能模块中同样是使用

  • 第1步: api目录下面文件里面封装请求方法
  • 第2步:data里面定义变量用于存储数据
  • 第3步:methods里定义获取数据方法
    • 3.1 页面里面导入api目录里封装请求方法
    • 3.2 methods方法里面调用方法发送请求
    • 3.3 请求错误处理
    • 3.4 请求成功赋值data里面变量
  • 第4步:created里面调用这个方法
  • 第5步: 渲染数据

第1步: api目录下面文件里面封装请求方法 api/user.js

import store from "@/store"

// 其他代码...

/**
 * 获取用户自己的信息
 */
export const getUserInfo = () => {
  return request({
    method: 'GET',
    url: '/v1_0/user',
    // 发送请求头数据
    headers: {
      // 注意:该接口需要授权才能访问
      //       token的数据格式:Bearer token数据,注意 Bearer 后面有个空格
      Authorization: `Bearer ${store.state.user.token}`
    }
  })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

第2步: data里面定义变量用于存储数据

 data () {
    return {
      userInfo: {} //  data里面定义用户信息变量
    }
  }
1
2
3
4
5

第3步:methods里定义获取数据方法

methods:{
 // 获取用户信息   
 async loadUserInfo () {

 }
}
1
2
3
4
5
6
  • 3.1 在 views/my/index.vue 导入这个方法
import { getUserInfo } from '@/api/user' 
1
  • 3.2 methods方法里面调用方法发送请求
async loadUserInfo () {
    try {
    	const { data } = await getUserInfo()
        // 2.3 成功赋值
    	this.userInfo = data.data
    } catch (err) {
        // 2.4 错误处理 
    	this.$toast('获取数据失败,请稍后重试')
    }
}
1
2
3
4
5
6
7
8
9
10

第4步:created里面调用这个方法

  created () {
    // 如果用户登录了,则请求加载用户信息数据
    if (this.user) {
      this.loadUserInfo()
    }
  }
1
2
3
4
5
6

第5步: 渲染数据

<!-- 已登录头部 -->
<div v-if="user" class="header user-info">
    <div class="base-info">
        <div class="left">
            <van-image
                       class="avatar"
                       :src="userInfo.photo"
                       round
                       fit="cover"
                       />
            <span class="name">{{ userInfo.name }}</span>
        </div>
        <div class="right">
            <van-button size="mini" round>编辑资料</van-button>
        </div>
    </div>
    <div class="data-stats">
        <div class="data-item">
            <span class="count">{{ userInfo.art_count }}</span>
            <span class="text">头条</span>
        </div>
        <div class="data-item">
            <span class="count">{{ userInfo.follow_count }}</span>
            <span class="text">关注</span>
        </div>
        <div class="data-item">
            <span class="count">{{ userInfo.fans_count }}</span>
            <span class="text">粉丝</span>
        </div>
        <div class="data-item">
            <span class="count">{{ userInfo.like_count }}</span>
            <span class="text">获赞</span>
        </div>
    </div>
</div>
<!-- /已登录头部 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

整个 user/index.vue完整代码

<template>
  <div class="my-container">
    <!-- 已登录头部 -->
    <div v-if="user" class="header user-info">
      <div class="base-info">
        <div class="left">
          <van-image
            class="avatar"
            :src="userInfo.photo"
            round
            fit="cover"
          />
          <span class="name">{{ userInfo.name }}</span>
        </div>
        <div class="right">
          <van-button size="mini" round>编辑资料</van-button>
        </div>
      </div>
      <div class="data-stats">
        <div class="data-item">
          <span class="count">{{ userInfo.art_count }}</span>
          <span class="text">头条</span>
        </div>
        <div class="data-item">
          <span class="count">{{ userInfo.follow_count }}</span>
          <span class="text">关注</span>
        </div>
        <div class="data-item">
          <span class="count">{{ userInfo.fans_count }}</span>
          <span class="text">粉丝</span>
        </div>
        <div class="data-item">
          <span class="count">{{ userInfo.like_count }}</span>
          <span class="text">获赞</span>
        </div>
      </div>
    </div>
    <!-- /已登录头部 -->

    <!-- 未登录头部 -->
    <div v-else class="header not-login">
      <div class="login-btn" @click="$router.push('/login')">
        <img class="mobile-img" src="~@/assets/mobile.png" alt="">
        <span class="text">登录 / 注册</span>
      </div>
    </div>
    <!-- /未登录头部 -->

    <!-- 宫格导航 -->
    <van-grid class="grid-nav mb-9" :column-num="2" clickable>
      <van-grid-item class="grid-item">
        <i slot="icon" class="toutiao toutiao-shoucang"></i>
        <span slot="text" class="text">收藏</span>
      </van-grid-item>
      <van-grid-item class="grid-item">
        <i slot="icon" class="toutiao toutiao-lishi"></i>
        <span slot="text" class="text">历史</span>
      </van-grid-item>
    </van-grid>
    <!-- /宫格导航 -->

    <van-cell title="消息通知" is-link />
    <van-cell class="mb-9" title="小智同学" is-link />
    <van-cell
      v-if="user"
      class="logout-cell"
      clickable
      title="退出登录"
      @click="onLogout"
    />
  </div>
</template>

<script>
import { mapState } from 'vuex'
import { getUserInfo } from '@/api/user'

export default {
  name: 'MyIndex',
  components: {},
  props: {},
  data () {
    return {
      userInfo: {} // 用户信息
    }
  },
  computed: {
    ...mapState(['user'])
  },
  watch: {},
  created () {
    // 如果用户登录了,则请求加载用户信息数据
    if (this.user) {
      this.loadUserInfo()
    }
  },
  mounted () {},
  methods: {
    onLogout () {
      // 退出提示
      // 在组件中需要使用 this.$dialog 来调用弹框组件
      this.$dialog.confirm({
        title: '确认退出吗?'
      }).then(() => {
        // on confirm
        // 确认退出:清除登录状态(容器中的 user + 本地存储中的 user)
        this.$store.commit('setUser', null)
      }).catch(() => {
        // on cancel
        console.log('取消执行这里')
      })
    },

    async loadUserInfo () {
      try {
        const { data } = await getUserInfo()
        this.userInfo = data.data
      } catch (err) {
        this.$toast('获取数据失败,请稍后重试')
      }
    }
  }
}
</script>

<style scoped lang="less">
.my-container {
  .header {
    height: 361px;
    background: url("~@/assets/banner.png");
    background-size: cover;
  }

  .not-login {
    display: flex;
    justify-content: center;
    align-items: center;
    .login-btn {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      .mobile-img {
        width: 132px;
        height: 132px;
        margin-bottom: 15px;
      }
      .text {
        font-size: 28px;
        color: #fff;
      }
    }
  }

  .user-info {
    .base-info {
      height: 231px;
      padding: 76px 32px 23px;
      box-sizing: border-box;
      display: flex;
      justify-content: space-between;
      align-items: center;
      .left {
        display: flex;
        align-items: center;
        .avatar {
          width: 132px;
          height: 132px;
          border: 4px solid #fff;
          margin-right: 23px;
        }
        .name {
          font-size: 30px;
          color: #fff;
        }
      }
    }
    .data-stats {
      display: flex;
      .data-item {
        height: 130px;
        flex: 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        color: #fff;
        .count {
          font-size: 36px;
        }
        .text {
          font-size: 23px;
        }
      }
    }
  }

  .grid-nav {
    .grid-item {
      height: 141px;
      i.toutiao {
        font-size: 45px;
      }
      .toutiao-shoucang {
        color: #eb5253;
      }
      .toutiao-lishi {
        color: #ff9d1d;
      }
      span.text {
        font-size: 28px;
      }
    }
  }

  .logout-cell {
    text-align: center;
    color: #d86262;
  }

  .mb-9 {
    margin-bottom: 9px;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

# 7 优化设置 Token

项目中的接口除了登录之外大多数都需要提供 token 才有访问权限。

通过接口文档可以看到,后端接口要求我们将 token 放到请求头 Header 中并以下面的格式发送。

image-20200301214857543

字段名称:Authorization

字段值:Bearer token,注意 Bearertoken 之间有一个空格

方式一:在每次请求的时候手动添加(麻烦)。

axios({
  method: "",
  url: "",
  headers: {
    Authorization: "Bearer token"
  }
})
1
2
3
4
5
6
7

方式二:使用请求拦截器统一添加(推荐,更方便)。

axios文档地址:http://axios-js.com/zh-cn/docs/#%E6%8B%A6%E6%88%AA%E5%99%A8

sequenceDiagram
	participant A as 发起请求
	participant B as 请求拦截器
	participant C as 服务端
  A-->>B: http://xxx
  Note right of B: 设置 token
  B->>C: 请求发出
  
  
1
2
3
4
5
6
7
8
9

src/utils/request.js 中添加拦截器统一设置 token:

/**
 * 请求模块
 */
import axios from 'axios'
import store from '@/store'   // <===========增加

const request = axios.create({
  baseURL: 'http://toutiao-app.itheima.net/' // 接口的基准路径
})

// 请求拦截器   <===========增加
request.interceptors.request.use(function (config) {
  // config :本次请求的配置对象
  // config 里面有一个属性:headers
  const { user } = store.state
  if (user && user.token) {
    config.headers.Authorization = `Bearer ${user.token}`
  }
  return config
}, function (error) {
  return Promise.reject(error)
})

// 响应拦截器


export default request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

api/user.js 里面的方法进行重构测试

//import store from "@/store"  // <====删除

// 其他代码....

/**
 * 获取用户自己的信息
 */
export const getUserInfo = () => {
  return request({
    method: 'GET',
    url: '/v1_0/user',
    // 发送请求头数据           <==== 删除
    /*  
    headers: {
      // 注意:该接口需要授权才能访问
      //       token的数据格式:Bearer token数据,注意 Bearer 后面有个空格
      Authorization: `Bearer ${store.state.user.token}`
    }
    */
  })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22