需要实现的主要功能如下:
资讯列表、标签页切换,文章举报,频道管理、文章详情、阅读记忆,关注功能、点赞功能、评论功能、回复评论、搜索功能、登录功能、个人中心、编辑资料、小智同学 ...
首先回顾一下:昨天我们实现了登录,登录拦截,以及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测试路由之间的跳转
测试功能正常实现,但是有一行警告出现
我们将
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遍历即可 最终实现的目标如下图
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”
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调试工具,检测结果
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
有以下三种状态:非加载中,
loading
为false
,此时会根据列表滚动位置判断是否触发load
事件(列表内容不足一屏幕时,会直接触发)
- 加载中,
loading
为true
,表示正在发送异步请求,此时不会重复触发load
事件- 加载完成,
finished
为true
,此时将再也不会触发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测试效果
6 获取数据补上时间戳
每次获取的数据都是相同的 每次请求10条数据都是同10条,按照后端文档给的提示,发现每次请求需要传递精确的时间戳,而每次返回的数据中正好包括时间戳,正是我们需要发送给后端的时间戳数据
因此:
- 补充一个数据项timestamp,初值为当前时间戳
- 在发请求时带上它
- 数据回来之后,更新它
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看一下效果图
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指令,这里的懒加载将会报错。