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

1,067 阅读7分钟

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

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

首先回顾一下:昨天我们实现了登录,登录拦截,以及layout组件的搭建

注意:(数据默认都会存在data.data中,本文默认使用解构)

今天我们要实现的功能主要是:资讯列表

1 实现二级路由

1.1在src/views 下创建四个文件夹分别为home,question,setting,video

在四个文件夹中分别创建 home.vue, question.vue, setting.vue, video.vue 在每个文件中建立起初始代码在其中用h1标签做好标记,方便之后测试

1.2嵌套路由

src/router/index.js,增加代码如下

{
    path: '/',
    name: 'layout',
    component: () => import('../views/layout/layout.vue'),
    // 二级路由
    children: [
      {
        path: '', // 二级路由的 path为空 表示默认要装入的组件
        name: 'home',
        component: () => import('../views/home/home.vue')
      },
      {
        path: '/question',
        name: 'question',
        component: () => import('../views/question/question.vue')
      },
      {
        path: '/video',
        name: 'video',
        component: () => import('../views/video/video.vue')
      },
      {
        path: '/setting',
        name: 'setting',
        component: () => import('../views/setting/setting.vue')
      }
    ]
  },

之前在van-tabbar标签中加入了route属性,它会自动帮我们实现跳转功能

1.3测试路由之间的跳转

image.png 测试功能正常实现,但是有一行警告出现

我们将router/index.js中配置首页的layout的name删除即可消除这项警告

2首页之频道列表

2.1 在src/views/home/home.vue中使用[tabs组件]来放置频道列表。

-->
<template>
  <div>
    <van-tabs v-model="active">
      <van-tab title="标签 1">内容 1</van-tab>
    </van-tabs>
    <!--1. tab -->
    <!--2. list -->

  </div>
</template>

<script>
export default {
  name: 'Index',
  data () {
    return {
      active: 1
    }
  }
}
</script>

van-tab标签只需要留下一个使用v-for遍历即可 最终实现的目标如下图

image.png

2.2封装api请求

之前测试axios时我们封装过的channel.js,我们拿出来复用,内容代码如下:

// 处理频道相关操作
import request from '@/utils/request'

/**
 * 获取频道列表
 */
export const getChannels = () => {
  return request({
    url: 'v1_0/user/channels',
    method: 'GET'
  })
}

2.3调用api获取数据

**在views/home/home.vue中 **

//导入封装好的channel.js
import { getChannels } from '@/api/channel.js'
export default {
  data () {
    return {
      active: 2, // tabs默认选中的下标
      channels: [] // 频道列表
    }
  },
  created () {
    this.loadChannels()
  },
  methods: {
    async loadChannels () {
      try {
        const {data:{data}} = await getChannels()
        this.channels = data.channels
      } catch (err) {
        console.log(err)
      }
    }
  }
}

2.4渲染视图

<van-tab
       v-for="channel in channels"
       :title="channel.name"
       :key="channel.id">
    频道内容 {{ channel.name }}
  </van-tab>

查看效果达到2.1中实现的最终目标图

3 频道内容的样式

我们在对应频道中添加内容:

<div>
   频道内容 {{ channel.name }}
   <p v-for="idx in 20" :key="idx">第{{idx}}篇文章</p>
</div>

可以看到效果图中滚动条出现在了屏幕的最上端,以及顶部导航会随着滑动隐藏,为了解决这些问题,我们需要再公共样式中继续加入一些样式解决这些问题

styles/index.less中加入如下样式代码(注意:使用前要给目标加入固定类名)

  • app.vue的根元素的id是app

  • layout.vue的根元素添加container类

  • home.vue的根元素添加index类

  • scroll-wrapper加在频道内容的容器上

/** 最外层的容器撑满整个屏幕 **/
#app{
  position: absolute;
  left: 0;
  top: 0;
  overflow: hidden;
  width: 100%;
  height: 100%;
}
/** .container是Layout.vue组件的根元素的类名 **/
.container {
  width: 100%;
  height: 100%;
}
/** .index 是home/home.vue组件的根元素的类名 **/
.index {
  height: 100%;
  // 顶部在导航条是固定定位的,让出顶部导航条的距离
  padding-top: 46px;
  .van-tabs {
    padding-top:50px; 
    display: flex;
    flex-direction: column;
    height: 100%;
    .van-tabs__wrap {
      position:fixed;
      top:46px; 
      /** 跟在顶部固定导航条的下方 **/
      left:0px;
      right:30px; 
      /** 高亮当前选中频道的下划线 **/
      .van-tabs__line {
        width: 30px !important;
        background-color: #3296fa;
        bottom: 20px;
      }
    }
    .van-tabs__content {
      flex:1;
      overflow: hidden;
      padding-bottom: 4rem;  /** 能看到文章列表中的loading效果 **/
      .van-tab__pane{ 
        height: 100%;
        /** .scroll-wrapper 是home/ArticleList.vue组件的根元素的类名 **/
        .scroll-wrapper{
          overflow:auto;
          height: 100%;
        }
      }
    }
  }
}

4 实现文章列表的上拉加载

4.1 我们单独抽出来一个组件 articleList.vue 来处理

在home文件夹中新建一个articleList.vue组件来处理文章列表的一系列功能

<template>
  <div class="scroll-wrapper">
    文章列表组件 {{Math.random()}}
    <p v-for="i in 50" :key="i">{{i}}</p>
  </div>
</template>

<script>
export default {
  name: 'ArticleList'
}
</script>

<style scoped lang='less'></style>

4.2 和home.vue文件建立关系

在home.vue中

import ArticleList from './articleList'
//注册组件
components: {
    ArticleList
},
<template>
  <div class="index">
    <!-- 频道列表
    https://vant-contrib.gitee.io/vant/#/zh-CN/tab#biao-qian-lan-gun-dong
    -->
    <van-tabs>
      <van-tab
        v-for="channel in channels"
        :title="channel.name"
        :key="channel.id">
<!-- 频道与文章列表是一一对应的。每个频道都需要有一个文章列表组件。
article-list写在了v-for中,则每次循环都会生成一个文章列表组件。
van-tab具有类似于 懒加载的效果: 只有激活了当前的tab,才会去创建文章列表组件 -->
+       <article-list></article-list>

      </van-tab>
    </van-tabs>

    <!-- 文章列表 -->
  </div>
</template>

van-tabs有类似于懒加载的效果:“只有激活了某个标签,才会去加载ArticleList”

image.png

4.3文章列表-频道信息传递(父传子)

在home.vue 中传入channel

<article-list :channel="channel"></article-list>

在子组件articleList中接收prop

props: ['channel']
<div class="scroll-wrapper">
  {{channel}}频道的文章信息列表组件
  <p v-for="i in 50" :key="i">{{i}}</p>
</div>

4.4使用vue调试工具,检测结果

image.png

5 文章列表-van-list

5.1在articleList.vue中

<div class="scroll-wrapper">
    {{channel}}频道的文章信息列表组件
    <!--
      van-list自带上拉加载更多 的效果
      原理:
        1)数据项在list中
        2)在显示数据时,如果当前的数据不足一屏,它会 自动触发load事件,并执行对应的回调onLoad去加载数据
           在onload中,通过ajax取回新数据
           - 把数据追加到list中(list的内容会越来越多)
           - 把loading手动设置为false
           - 判断是否所有的数据已经加载完成,如果是,则把finished设为true
        3) 如果手动向上拉,且finished不为true, 也会去调用onLoad
    -->
    <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>
<script>
 data () {
    return {
      list: [], // 数据项
      loading: false, // 是否正在加载...
      finished: false // 是否所有的数据全部加载完成
    }
  },
  methods: {
    onLoad () {
      // 异步更新数据
      // setTimeout 仅做示例,真实场景中一般为 ajax 请求
      setTimeout(() => {
        for (let i = 0; i < 10; i++) {
          this.list.push(this.list.length + 1)
        }

        // 加载状态结束
        this.loading = false

        // 数据全部加载完成
        if (this.list.length >= 40) {
          this.finished = true
        }
      }, 1000)
    }
  }
</script>

5.2 van-list组件的[数据加载机制]

  • 如果当前的内容不够一屏,它会自动调用onLoad事件的回调函数去加载数据,并填入数据。

  • List有以下三种状态:

  • 非加载中,loadingfalse,此时会根据列表滚动位置判断是否触发load事件(列表内容不足一屏幕时,会直接触发)

    • 加载中,loadingtrue,表示正在发送异步请求,此时不会重复触发load事件
    • 加载完成,finishedtrue,此时将再也不会触发load事件
  • 在每次请求完毕后,需要手动将loading设置为false,表示本次加载结束

5.3 获取数据并显示

在src/api目录下创建article.js文件,封装用来处理与文章的请求函数。

import request from '@/utils/request.js'

// eslint-disable-next-line camelcase
export const getArticles = (channel_id, timestamp) => {
  // 如果函数没有写return,
  //  它有一个默认的return值,就是undefined
  // return request({
  return request({
    url: 'v1_0/articles', // 接口地址
    method: 'GET',
    params: {
      channel_id,
      // 时间戳,请求新的推荐数据传当前的时间戳,请求历史推荐传指定的时间戳
      timestamp
    }
  })
}

5.4 在articleList.vue中,导入这个接口

import { getArticles } from '@/api/article.js'
  const {data:{data}} = await getArticles(this.channel.id, Date.now())
  // 获取的数据
  const arr = data.results // 它是一个数组
  // 1. 追加数据到list
  //    对数组进行展开
  this.list.push(...arr)
  // 2. 把loading设置为false
  this.loading = false
  // 3. 判断是否所有的数据全部加载完成,如果是:finished设为true
  if (arr.length === 0) {
    // 说明取不到数据了
    this.finished = true
  }
  // 4. 页面上提示一下用户
  this.$toast.success('成功加载数据')
}

因为list中的每一项都是一个对象,表示一篇文章,所以,要调整一下v-for指令中的key, title。

<van-list
      v-model="loading"
      :finished="finished"
      loading-text="一大波数据正在赶过来"
      finished-text="讨厌,人家被你看完了"
      @load="onLoad">
  
      <van-cell v-for="article in list"
      :key="article.art_id"
      :title="article.title" />
</van-list>

5.5测试效果

image.png

6 获取数据补上时间戳

每次获取的数据都是相同的 每次请求10条数据都是同10条,按照后端文档给的提示,发现每次请求需要传递精确的时间戳,而每次返回的数据中正好包括时间戳,正是我们需要发送给后端的时间戳数据

因此:

  1. 补充一个数据项timestamp,初值为当前时间戳
  2. 在发请求时带上它
  3. 数据回来之后,更新它
data(){
    return{
+       tiemstamp:Date.now()
    }
}

async onLoad () {
      // Date.now(): 请求新的推荐数据传当前的时间戳
+     const {data:{data}} = await getArticles(this.channel.id, this.tiemstamp)
      // 获取的数据
      const arr = data.results // 它是一个数组
      // 1. 追加数据到list
      //    对数组进行展开
      this.list.push(...arr)

      // 2. 更新时间戳
+     this.tiemstamp = data.pre_timestamp
      // 3. 把loading设置为false
      this.loading = false
      // 4. 判断是否所有的数据全部加载完成,如果是:finished设为true
      if (arr.length === 0) {
        // 说明取不到数据了
        this.finished = true
      }
      // 5. 页面上提示一下用户
      this.$toast.success('成功加载数据')
    }

7 文章项布局

  • van-cell 中的插槽: 作用是整体包裹,显示在标题下方,用来把图片-作者-评论-时间等全显示在标题的下方。
  • van-grid :。用它可以将一行均分几列
  • van-image:增强版的image,用它来显示图片

7.1 结构代码

<van-cell
        v-for="item in list"
        :key="item.art_id"
        :title="item.title">
        <div slot="label">
          <van-grid v-if="item.cover.images" :column-num="item.cover.images.length">
            <van-grid-item
              v-for="(imgSrc,idx) in item.cover.images"
              :key="idx">
              <!-- {{imgSrc}} -->
              <van-image :src="imgSrc"/>
            </van-grid-item>
          </van-grid>

          <!-- 文字区域 -->
          <div class="meta">
            <span>{{item.aut_name}}</span>
            <span>{{item.comm_count}}评论</span>
            <span>{{item.pubdate}}</span>
          </div>

        </div>
      </van-cell>

7.2 加入样式

  • 从后端取出来的每篇文章中,最多有三张图。具体分成如下三种情况:三图,一图,无图。
  • 图片保存在cover.images中。

在底部加入样式

<style scoped lang='less'>
.meta {
  span{
    margin-right: 10px;
  }
}
</style>

8 下拉刷新功能(还存在写后端的bug因此刷新还会报错)错误内容因为数据返回的是重复的

当用户向下拉动页面时,用当前的时间戳去请求接口,以得到最新的数据,再把数据填充到页面的最上方,以实现下拉刷新的功能

8.1结构 只要用van-pull-refresh标签将你的内容包裹起来就可以实现,所以直接在van-list的外面包一个van-pull-refresh即可。

<template>
  <div class="scroll-wrapper">
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
      <van-list>
      	.......
      </van-list>
+    </van-pull-refresh>
  </div>
</template>

8.2在data中补充一个isLoadingNew

data () {
    return {
      list: [], // 数据,每一项就是一篇文章
      timestamp: null,
+     refreshing: false, // 是否正在下拉刷新
      loading: false, // 是否正在加载
      finished: false // 是否所有的数据全部加载完成
    }
  }

8.3下拉时,也需要发送请求,让后端返回最新的数据给我们

// 下拉刷新,去获取最 新 的数据,添加到列表中
    async onRefresh () {
      // Date.now(): 请求新的推荐数据传当前的时间戳
      const res = await getArticles(this.channel.id, Date.now())
      // 获取的数据
      const arr = res.data.data.results // 它是一个数组
      // 1. 追加数据到list的头部
      //    对数组进行展开
      this.list.unshift(...arr)

      // 2. 把loading设置为false
      this.refreshing = false

      this.$toast.success('成功加载数据')
    },
  • 要设置getArticles的第二个参数(timestamp)是最新的时间戳
  • unshift: 把数据放在数组的头部

8.4看一下效果图

image.png

9 时间过滤器

封装一个时间过滤器,用Vue.use()方法定义到全局 显示几天前或是几小时前或是刚刚发布的

9.1 了解一下过滤器的基本格式

filters: {
  过滤器名 (要被处理的值) {
    // .... 
    return 处理后的结果
  }
}

使用:

// 相当于 {{ 过滤器名(原数据) }}

9.2 封装自己的过滤器函数

在utils文件夹中新建一个自己的工具包文件,这里命名为dateFormate.js 写入自己封装的函数并导出

export const relativeTime = (value) => {
      const t = new Date(value)
      const diff = Date.now() - t.getTime()

      const year = Math.floor(diff / (1000 * 3600 * 24 * 365))
      if (year) {
        return `${year}年前`
      }
      const month = Math.floor(diff / (1000 * 3600 * 24 * 30))
      if (month) {
        return `${month}月前`
      }
      const day = Math.floor(diff / (1000 * 3600 * 24))
      if (day) {
        return `${day}天前`
      }
      const hour = Math.floor(diff / (1000 * 3600))
      if (hour) {
        return `${hour}小时前`
      }
      const minute = Math.floor(diff / (1000 * 60))
      if (minute) {
        return `${minute}分钟前`
      } else {
        return '刚才'
      }
    }

在其后默认导出一个对象调用install方法传参Vue

export default {
  install: function (Vue) {
    Vue.filter('dateFormate', dateFormate)
  }
}

9.3 在main.js中引入这个工具文件,并用Vue.use()调用即可实现

import dateFormat from './utils/dateFormate.js'
Vue.use(dateFormat)

注意:此处dataFormate为自定义名字 在使用过滤器时不能用它而必须用自己封装函数的名字:relativeTime

9.4 在articleList.vue 中使用自定义的过滤器

 <!-- 文字区域 -->
    <div class="meta">
        <span>{{ item.aut_name }}</span>
        <span>{{ item.comm_count }}评论</span>
 +      <span>{{ item.pubdate | dateFormate }}</span>
    </div>

10 图片懒加载

利用vant组件的Lazyload来实现图片懒加载 在main.js中引入

 import Vant, { Lazyload } from 'vant'
 Vue.use(Lazyload)

在 articleList.vue组件中使用

   <van-grid-item v-for="(img,idx) in item.cover.images" :key="idx">
+    <van-image lazy-load :src="img" />
   </van-grid-item>

注意:如果不提前引入Lazyload指令,这里的懒加载将会报错。