# 四、首页—文章列表

# 1、页面布局

# 1.1、头部导航栏

1、使用导航栏组件

2、在导航栏组件中插入按钮

  • 文本
  • 图标

3、样式调整

  • 宽高
  • 背景色
  • 边框
  • 文本大小
  • 图标大小
<template>
  <div class="home-container">
    <!-- 导航栏 -->
    <van-nav-bar class="page-nav-bar" fixed>
      <van-button
        class="search-btn"
        slot="title"
        type="info"
        size="small"
        round
        icon="search"
      >搜索</van-button>
    </van-nav-bar>
    <!-- /导航栏 -->
  </div>
</template>

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

<style scoped lang="less">
.home-container {
  padding-top: 174px;
  padding-bottom: 100px;
  /deep/ .van-nav-bar__title {
    max-width: unset;
  }
  .search-btn {
    width: 555px;
    height: 64px;
    background-color: #5babfb;
    border: none;
    font-size: 28px;
    .van-icon {
      font-size: 32px;
    }
  }
}
</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

# 2、频道列表

image-20200305195458197

# 2.1、使用 Tab 标签页组件

参考:Tab 标签页组件 (opens new window)

# 2.2、样式调整

(1)基础样式调整

  • 标签项
    • 右边框
    • 下边框
    • 宽高
    • 文字大小
    • 文字颜色
  • 底部条
    • 宽高
    • 颜色
    • 位置

(2)处理汉堡按钮

1、使用插槽插入内容

2、样式调整

  • 定位
  • 内容居中
  • 宽高
  • 背景色、透明度
  • 字体图标大小

3、使用伪元素设置渐变边框

  • 定位
  • 宽高
  • 背景图
  • 背景图填充模式

4、添加占位符充当内容区域

image-20200402215736215

HTML结构

<!-- 频道列表 -->
<!-- 
animated 滑动的动画
border 底边框线
swipeable 开启左右手势滑动
-->
<van-tabs  class="channel-tabs" v-model="active" swipeable animated border>
    <van-tab title="标签 1">内容 1内容 1内容 1内容 1内容 1内容 1内容 1内容 1内容 1</van-tab>
    <van-tab title="标签 2">内容 2内容 1内容 1内容 1内容 1内容 1内容 1</van-tab>
    <van-tab title="标签 3">内容 3内容 1内容 1内容 1内容 1内容 1</van-tab>
    <van-tab title="标签 4">内容 4</van-tab>
    <van-tab title="标签 6">内容 6</van-tab>
    <van-tab title="标签 7">内容 7</van-tab>
    <van-tab title="标签 8">内容 8</van-tab>
    <!-- 右侧自定义内容 -->
    <!-- 占位元素 -->
    <div slot="nav-right" class="placeholder"></div>
    <!-- 右侧按钮 -->
    <div slot="nav-right" class="hamburger-btn">
        <i class="toutiao toutiao-gengduo"></i>
    </div>
</van-tabs>
<!-- /频道列表 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

CSS 样式:

  /deep/ .channel-tabs {
    .van-tabs__wrap {
      position: fixed;
      top: 92px;
      z-index: 1;
      left: 0;
      right: 0;
      height: 82px;
    }

    .van-tab {
      border-right: 1px solid #edeff3;
      min-width: 200px;
      font-size: 30px;
      color: #777777;
    }

    .van-tab--active {
      color: #333333;
    }

    .van-tabs__nav {
      padding-bottom: 0;
    }

    .van-tabs__line {
      bottom: 8px;
      width: 31px !important;
      height: 6px;
      background-color: #3296fa;
    }

    .placeholder {
      flex-shrink: 0;
      width: 66px;
      height: 82px;
    }

    .hamburger-btn {
      position: fixed;
      right: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 66px;
      height: 82px;
      background-color: #fff;
      background-color: rgba(255, 255, 255, 0.902);
      i.toutiao {
        font-size: 33px;
      }
      &:before {
        content: "";
        position: absolute;
        left: 0;
        width: 1px;
        height: 58px;
        background-image: url(~@/assets/gradient-gray-line.png);
        background-size: contain;
      }
    }
  }
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

注: 深度选择器 https://vue-loader.vuejs.org/zh/guide/scoped-css.html#%E6%B7%B1%E5%BA%A6%E4%BD%9C%E7%94%A8%E9%80%89%E6%8B%A9%E5%99%A8

# 2.3、展示频道列表

思路:

  • 第1步:api目录下面文件封装请求方法
  • 第2步:data里面定义变量用于存储数据
  • 第3步:methods里定义获取数据方法
  • 3.1 页面里面导入这个方法
  • 3.2 methods方法里面调用方法发送请求
  • 3.3 请求错误处理
  • 3.4 请求成功赋值data里面变量
  • 第4步:created里面调用这个方法
  • 第5步: 渲染数据
  • 1 api/user.js 里面封装
/**
 * 获取用户自己的信息
 */
export const getUserChannels = () => {
  return request({
    method: 'GET',
    url: '/v1_0/user/channels'
  })
}
1
2
3
4
5
6
7
8
9
  • 2、data 里面定义变量
data () {
    return {
      channels: [] // 频道列表
    }
  },
1
2
3
4
5
  • 3、methods 定义方法
// 导入封装方法
import { getUserChannels } from '@/api/user'

methods: {
    async loadChannels () {
      try {
        // 发请求
        const { data } = await getUserChannels()
        // 成功赋值
        this.channels = data.data.channels
      } catch (err) {
        // 失败处理
        this.$toast('获取频道数据失败')
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 4、生命周期created里面调用
 created () {
    this.loadChannels()
  },
1
2
3
  • 5、渲染数据
 <!-- 频道列表 -->
    <!-- 
      animated 滑动的动画
      border 底边框线
      swipeable 开启左右手势滑动
     -->
    <van-tabs  class="channel-tabs" v-model="active" swipeable animated border>
      
       <van-tab v-for="channel in channels" :key="channel.id" :title="channel.name">
          {{channel.name}}的内容
       </van-tab>
        
      <!-- ...其他内容 -->
    </van-tabs>
    <!-- /频道列表 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3、文章列表

image-20200311221143472

# 3.1、思路分析

你的思路可能是这样的:

1、找到数据接口

2、封装请求方法

3、在组件中请求获取数据,将数据存储到 data 中

4、模板绑定展示

根据不同的频道加载不同的文章列表,你的思路可能是这样的:

  • 有一个 list 数组,用来存储文章列表
  • 查看 a 频道:请求获取数据,让 list = a 频道文章
  • 查看 b 频道:请求获取数据,让 list = b 频道文章
  • 查看 c 频道:请求获取数据,让 list = c 频道文章
  • ...

image-20200111124415227

思路没有问题,但是并不是我们想要的效果。

我们想要的效果是:加载过的数据列表不要重新加载

实现思路也非常简单,就是我们准备多个 list 数组,每个频道对应一个,查看哪个频道就把数据往哪个频道的列表数组中存放,这样的话就不会导致覆盖问题

image-20200111124530323

可是有多少频道就得有多少频道文章数组,我们都一个一个声明的话会非常麻烦,所以这里的建议是利用组件来处理。

具体做法就是:

  • 封装一个文章列表组件
  • 然后在频道列表中把文章列表遍历出来

因为文章列表组件中请求获取文章列表数据需要频道 id,所以 频道 id 应该作为 props 参数传递给文章列表组件,为了方便,我们直接把频道对象传递给文章列表组件就可以了。

image-20200306155328108

在文章列表中请求获取对应的列表数据,展示到列表中。

最后把组件在频道列表中遍历出来,就像下面这样。

image-20200306154805522

# 3.2、创建文章列表组件

  • 1、创建 src/views/home/components/article-list.vue
<template>
  <div class="article-list">文章列表</div>
</template>

<script>
export default {
  name: 'ArticleList',
  components: {},
  props: {
    // 设置一个props用来接收属于自己的频道信息数据  
    channel: {
      type: Object,
      required: true
    }
  },
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>

<style scoped lang="less">
.article-list {
  // 百分比单位是相对于父元素的
  // height: 100%;

  // 视口(在移动端是布局视口)单位:vw 和 vh,不受父元素影响
  // 1vw = 视口宽度的百分之一
  // 1vh = 视口高度的百分之一
  height: 79vh;
  overflow-y: auto;
}
</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

2、home/index.vue 中,导入、注册、使用

image-20200305205441485

3、最后测试,我们可以通过vue调试器,观察是否切换是否就有不同的 列表组件,且props是否收到了对应数据内容。

答疑:

  • 为什么标签内容是懒渲染的?
    • 因为这是 Tab 标签页组件本身支持的默认功能,如果不需要可以通过配置 :lazy-render="false" 来关闭这个效果。

# 3.3、使用 List 列表组件

List 列表组件 (opens new window):瀑布流滚动加载,用于展示长列表。

List 组件通过 loading 和 finished 两个变量控制加载状态, 当组件初始化或滚动到到底部时,会触发 load 事件并将 loading 设置成 true,此时可以发起异步操作并更新数据,数据更新完毕后,将 loading 设置成 false 即可。 若数据已全部加载完毕,则直接将 finished 设置成 true 即可。

  • load 事件
    • List 初始化后会触发一次 load 事件,用于加载第一屏的数据。
    • 如果一次请求加载的数据条数较少,导致列表内容无法铺满当前屏幕,List 会继续触发 load 事件,直到内容铺满屏幕或数据全部加载完成。
  • loading 属性:控制加载中的 loading 状态
    • 非加载中,loading 为 false,此时会根据列表滚动位置判断是否触发 load 事件(列表内容不足一屏幕时,会直接触发)
    • 加载中,loading 为 true,表示正在发送异步请求,此时不会触发 load 事件
  • finished 属性:控制加载结束的状态
    • 在每次请求完毕后,需要手动将 loading 设置为 false,表示本次加载结束
    • 所有数据加载结束,finished 为 true,此时不会触发 load 事件
<template>
  <div class="article-list">
    <!--
      List 列表组件:瀑布流滚动加载,用于展示长列表。

      List 组件通过 loading 和 finished 两个变量控制加载状态,
      当组件初始化或滚动到到底部时,会触发 load 事件并将 loading 自动设置成 true,此时可以发起异步操作并更新数据,
      数据更新完毕后,将 loading 设置成 false 即可。
      若数据已全部加载完毕,则直接将 finished 设置成 true 即可。

      - load 事件:
        + List 初始化后会触发一次 load 事件,用于加载第一屏的数据。
        + 如果一次请求加载的数据条数较少,导致列表内容无法铺满当前屏幕,List 会继续触发 load 事件,直到内容铺满屏幕或数据全部加载完成。

      - loading 属性:控制加载中的 loading 状态
        + 非加载中,loading 为 false,此时会根据列表滚动位置判断是否触发 load 事件(列表内容不足一屏幕时,会直接触发)
        + 加载中,loading 为 true,表示正在发送异步请求,此时不会触发 load 事件

      - finished 属性:控制加载结束的状态
        + 在每次请求完毕后,需要手动将 loading 设置为 false,表示本次加载结束
        + 所有数据加载结束,finished 为 true,此时不会触发 load 事件
     -->
    <van-list
      v-model="loading"
      :finished="finished"
      finished-text="没有更多了"
      @load="onLoad"
    >
      <van-cell v-for="item in list" :key="item" :title="item" />
    </van-list>
  </div>
</template>

<script> 
export default {
  name: 'ArticleList',
  components: {},
  props: {
    channel: {
      type: Object,
      required: true
    }
  },
  data () {
    return {
      list: [], // 存储列表数据的数组
      loading: false, // 控制加载中 loading 状态
      finished: false // 控制数据加载结束的状态
    }
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {
    // 初始化或滚动到底部的时候会触发调用 onLoad
    onLoad () {
      console.log('onLoad')
      // 1. 请求获取数据
      // setTimeout 仅做示例,真实场景中一般为 ajax 请求
      setTimeout(() => {
        // 2. 把请求结果数据追加放到 list 数组中  【一定要记住是追加,因为只有追加才会让列表高度不断增加】
        for (let i = 0; i < 10; i++) {
          // 0 + 1 = 1
          // 1 + 1 = 2
          // 2 + 1 = 3
          this.list.push(this.list.length + 1)
        }

        // 3. 本次数据加载结束之后要把加载状态设置为结束
        //     loading 关闭以后才能触发下一次的加载更多
        this.loading = false

        // 4. 判断数据是否全部加载完成
        if (this.list.length >= 40) {
          // 如果没有数据了,把 finished 设置为 true,之后不再触发加载更多
          this.finished = true
        }
      }, 1000)
    }
  }
}
</script>
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

# 3.4、加载文章列表数据

实现思路:

  • 找到数据接口
  • 封装请求方法
  • 请求获取数据
  • 模板绑定

1、创建 src/api/article.js 封装获取文章列表数据的接口

/**
 * 文章接口模块
 */
import request from '@/utils/request'

/**
 * 获取频道的文章列表
 */
export const getArticles = params => {
  return request({
    method: 'GET',
    url: '/v1_1/articles',
    params
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意:使用接口文档中最下面的 频道新闻推荐_V1.1

2、然后在首页文章列表组件 onload 的时候请求加载文章列表

<template>
  <div class="article-list">
      <!--
        loading 控制上拉加载更多的 loading 状态
        finished 控制数据是否加载结束
        load 事件:当触发上拉加载更多的时候会触发调用 load 事件

        List 初始化后会触发一次 load 事件,用于加载第一屏的数据
        如果一次请求加载的数据条数较少,导致列表内容无法铺满当前屏幕,List 会继续触发 load 事件,直到内容铺满屏幕或数据全部加载完成
      -->
      <van-list
        v-model="loading"
        :finished="finished"
        finished-text="没有更多了"
        :error.sync="error"
        error-text="请求失败,点击重新加载"
        @load="onLoad"
      >
        <van-cell
          v-for="(article, index) in list"
          :key="index"
          :title="article.title"
        />
      </van-list>
  </div>
</template>

<script>
import { getArticles } from '@/api/article'  // <====== 导入方法

export default {
  name: 'ArticleList',
  components: {},
  props: {
    // 设置一个props用来接收属于自己的频道信息数据    
    channel: {
      type: Object,
      required: true
    }
  },
  data () {
    return {
      list: [], // 文章列表数据
      loading: false, // 上拉加载更多的 loading 状态
      finished: false, // 是否加载结束
      error: false, // 是否加载失败
      timestamp: null // 请求下一页数据的时间戳
    }
  },
  methods: {
    // 当触发上拉加载更多的时候调用该函数
    async onLoad () {
      try {
        // 1. 请求获取数据
        const { data } = await getArticles({
          channel_id: this.channel.id, // 频道 id
          timestamp: this.timestamp || Date.now(), // 时间戳,请求新的推荐数据传当前的时间戳,请求历史推荐传指定的时间戳
          with_top: 1 // 是否包含置顶,进入页面第一次请求时要包含置顶文章,1-包含置顶,0-不包含
        })

        // 2. 把数据添加到 list 数组中(追加!!!)
        const { results } = data.data
        this.list.push(...results)

        // 3. 设置本次加载中 loading 状态结束
        this.loading = false

        // 4. 判断数据是否加载结束(处理页码)
        if (results.length) {
          // 更新获取下一页数据的时间戳
          this.timestamp = data.data.pre_timestamp
        } else {
          // 没有数据了,设置加载状态结束,不再触发上拉加载更多了
          this.finished = true
        }
      } catch (err) {
        console.log(err)
        this.loading = false // 关闭 loading 效果
        this.error = true // 开启错误提示
      }
    }
  }
}
</script>
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

最后测试。

# 3.5、下拉刷新

展示文章列表-下拉刷新

这里主要使用到 Vant 中的 PullRefresh 下拉刷新 (opens new window) 组件。

思路:

  • 注册下拉刷新事件(组件)的处理函数
  • 发送请求获取文章列表数据
  • 把获取到的数据添加到当前频道的文章列表的顶部
  • 提示用户刷新成功!

下拉刷新时会触发组件的 refresh 事件,在事件的回调函数中可以进行同步或异步操作,操作完成后将 v-model 设置为 false,表示加载完成。

<van-pull-refresh
      v-model="isreFreshLoading"
      :success-text="refreshSuccessText"
      :success-duration="1500"
      @refresh="onRefresh"
                  >
	 <van-list
        v-model="loading"
        :finished="finished"
        finished-text="没有更多了"
        :error.sync="error"
        error-text="请求失败,点击重新加载"
        @load="onLoad"
               >
    	<!--其他代码-->
    </van-list>
</van-pull-refresh>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data(){
    return{
        //... 其他变量
       isreFreshLoading: false, // 控制下拉刷新的 loading 状态
       refreshSuccessText: '刷新成功' // 下拉刷新成功提示文本
    }
}


// 当触发下拉刷新的时候调用该函数
async onRefresh () {
  try {
    // 1. 请求获取数据
    const { data } = await getArticles({
      channel_id: this.channel.id, // 频道 id
      timestamp: Date.now(), // 下拉刷新每次都应该获取最新数据
      with_top: 1 // 是否包含置顶,进入页面第一次请求时要包含置顶文章,1-包含置顶,0-不包含
    })

    // 2. 将数据追加到列表的顶部
    const { results } = data.data
    this.list.unshift(...results)

    // 3. 关闭下拉刷新的 loading 状态
    this.isreFreshLoading = false

    // 提示成功
    this.refreshSuccessText = `刷新成功,更新了${results.length}条数据`
  } catch (err) {
    console.log(err)
    this.isreFreshLoading = false // 关闭下拉刷新的 loading 状态
    this.$toast('刷新失败')
  }
}
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

# 4、文章列表项

# 4.1、准备组件

在我们项目中有好几个页面中都有这个文章列表项内容,如果我们在每个页面中都写一次的话不仅效率低而且维护起来也麻烦。所以最好的办法就是我们把它封装为一个组件,然后在需要使用的组件中加载使用即可。

1、创建 src/components/article-item/index.vue 组件

<template>
  <div class="article-item">文章列表项</div>
</template>

<script>
export default {
  name: 'ArticleItem',
  components: {},
  props: {
    // 定义一个props,接收这一行的数据
    article: {
      type: Object,
      required: true
    }
  },
  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
21
22
23
24
25
26
27
28

2、在文章列表组件中导入、注册、使用

image-20200404224728524

# 4.2、展示列表项内容

  • 使用 Cell 单元格组件
  • 展示标题
  • 展示底部信息
<template>
  <van-cell
    class="article-item"
  >
    <div slot="title" class="title">{{ article.title }}</div>
    <div slot="label">
      <div v-if="article.cover.type === 3" class="cover-wrap">
        <div
          class="cover-item"
          v-for="(img, index) in article.cover.images"
          :key="index"
        >
          <van-image
            width="100"
            height="100"
            :src="img"
          />
        </div>
      </div>
      <div>
        <span>{{ article.aut_name }}</span>
        <span>{{ article.comm_count }}评论</span>
        <span>{{ article.pubdate }}</span>
      </div>
    </div>
    <van-image
      v-if="article.cover.type === 1"
      slot="default"
      width="100"
      height="100"
      :src="article.cover.images[0]"
    />
  </van-cell>
</template>

<script>
export default {
  name: 'ArticleItem',
  components: {},
  props: {
    article: {
      type: Object,
      required: true
    }
  },
  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
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

# 4.3、样式调整

  • 文章标题
    • 字号
    • 颜色
    • 多行文字省略
  • 单图封面
    • 封面容器
      • 去除 flex: 1,固定宽高
      • 左内边距
    • 封面图
      • 宽高
      • 填充模式:cover
  • 底部文本信息
    • 字号
    • 颜色
    • 间距
  • 多图封面
    • 外层容器
      • flex 容器
      • 上下外边距
    • 图片容器
      • 平均分配容器空间:flex: 1;
      • 固定高度
      • 容器项间距
    • 图片
      • 宽高
      • 填充模式

以下代码仅供参考。

<template>
  <van-cell
    class="article-item"
  >
    <div slot="title" class="title van-multi-ellipsis--l2">{{ article.title }}</div>
    <div slot="label">
      <div v-if="article.cover.type === 3" class="cover-wrap">
        <div
          class="cover-item"
          v-for="(img, index) in article.cover.images"
          :key="index"
        >
          <van-image
            class="cover-item-img"
            fit="cover"
            :src="img"
          />
        </div>
      </div>
      <div class="label-info-wrap">
        <span>{{ article.aut_name }}</span>
        <span>{{ article.comm_count }}评论</span>
        <span>{{ article.pubdate }}</span>
      </div>
    </div>
    <van-image
      v-if="article.cover.type === 1"
      slot="default"
      class="right-cover"
      fit="cover"
      :src="article.cover.images[0]"
    />
  </van-cell>
</template>

<script>
export default {
  name: 'ArticleItem',
  components: {},
  props: {
    article: {
      type: Object,
      required: true
    }
  },
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>

<style scoped lang="less">
.article-item {
  .title {
    font-size: 32px;
    color: #3a3a3a;
  }

  .van-cell__title {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
  }

  .van-cell__value {
    flex: unset;
    width: 232px;
    height: 146px;
    padding-left: 25px;
  }

  .right-cover {
    width: 100%;
    height: 146px;
  }

  .label-info-wrap span {
    font-size: 22px;
    color: #b4b4b4;
    margin-right: 25px;
  }

  .cover-wrap {
    display: flex;
    padding: 30px 0;
    .cover-item {
      flex: 1;
      height: 146px;
      &:not(:last-child) {
        padding-right: 4px;
      }
      .cover-item-img {
        width: 100%;
        height: 146px;
      }
    }
  }
}
</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

# 4.4、第三方图片资源403问题

为什么文章列表数据中的好多图片资源请求失败返回 403?

这是因为我们项目的接口数据是后端通过爬虫抓取的第三方平台内容,而第三方平台对图片资源做了防盗链保护处理。
1

第三方平台怎么处理图片资源保护的?

服务端一般使用 Referer 请求头识别访问来源,然后处理资源访问。
1
image-20200405134347749

Referer 是什么东西?

扩展参考:http://www.ruanyifeng.com/blog/2019/06/http-referer.html

Referer 是 HTTP 请求头的一部分,当浏览器向 Web 服务器发送请求的时候,一般会带上 Referer,它包含了当前请求资源的来源页面的地址。服务端一般使用 Referer 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。
1

需要注意的是 referer 实际上是 "referrer" 误拼写。参见 HTTP referer on Wikipedia (opens new window) (HTTP referer 在维基百科上的条目)来获取更详细的信息。

怎么解决?

不要发送 referrer ,对方服务端就不知道你从哪来的了,姑且认为是你是自己人吧。
1

如何设置不发送 referrer?

<a><area><img><iframe><script> 或者 <link> 元素上的 referrerpolicy 属性为其设置独立的请求策略,例如:

<img src="http://……" referrerPolicy="no-referrer">
1

或者直接在 HTMl 页面头中通过 meta 属性全局配置public/index.html中书写以下内容:

<meta name="referrer" content="no-referrer" />
1

# 4.5、处理相对时间

推荐两个第三方库:

两者都是专门用于处理时间的 JavaScript 库,功能差不多,因为 Day.js 的设计就是参考的 Moment.js。但是 Day.js 相比 Moment.js 的包体积要更小一些,因为它采用了插件化的处理方式。

Day.js (opens new window) 是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js (opens new window) 的 API 设计保持完全一样,如果您曾经用过 Moment.js, 那么您已经知道如何使用 Day.js 。

  • Day.js 可以运行在浏览器和 Node.js 中。

  • 🕒 和 Moment.js 相同的 API 和用法

  • 💪 不可变数据 (Immutable)

  • 🔥 支持链式操作 (Chainable)

  • 🌐 国际化 I18n

  • 📦 仅 2kb 大小的微型库

  • 👫 全浏览器兼容

1、安装

npm i dayjs
1

2、创建 utils/dayjs.js

相对时间文档 (opens new window)

配置语言包文档 (opens new window)

import Vue from 'vue'
import dayjs from 'dayjs'

// 加载中文语言包
import 'dayjs/locale/zh-cn'

import relativeTime from 'dayjs/plugin/relativeTime'

// 配置使用处理相对时间的插件
dayjs.extend(relativeTime)

// 配置使用中文语言包
dayjs.locale('zh-cn')

// 全局过滤器:处理相对时间
Vue.filter('relativeTime', value => {
  return dayjs().to(dayjs(value))
})

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

3、在 main.js 中加载初始化

import './utils/dayjs'
1

4、使用

使用过滤器:

<span>{{ article.pubdate | relativeTime }}</span>
1