打印当前频道的文章列表可以看到以下数据,然后再渲染到页面上
了解文章数据源中的数据:
art_id:文章的 id
aut_id:作者的 id
aut_name :作者的名称
ch_id:所属频道的 id
collect_count:收藏数
comm_count:评论数
cover:文章的图片信息
images:数组,保存图片的路径
type:当前文章的图片的数量(0,1,3)
is_top:是否处于置顶状态
like_count:点赞数
pubdate:发布日期
title:文章的标题
组件image图片: 链接: link.
为什么要实现懒加载:
1.0 因为网速的原因:如果一次性将图片全部加载出来,网络差一点,页面打开非常慢
2.0 因为是移动端:如果加载出来的图片用户看不到,就没有意义,浪费流量。
使用 lazy-load 指令来实现懒加载
步骤:
2.0 向 vue 中注册这个 lazy-load
指令
import Vue from 'vue'
import { Lazyload } from 'vant'
Vue.use(Lazyload)
dayjs官网: link.
将具体时间改为相对时间(可以使用一个第三方包: dayjs (与 Moment.js 的用户基本一致))
dayjs:
作用:与 moment.js 一样,可以用来进行时间处理
特点:
使用步骤:
1.0 安装第三方包:npm install dayjs --save
2.0 导入 dayjs
: import dayjs from 'dayjs'
3.0 调用 API: dayjs().fromat('YYYY-MM-DD hh:mm:ss')
4.0 使用 dayjs 的相对时间插件
// 导入相对时间的插件
import relativeTime from 'dayjs/plugin/relativeTime'
// 将插件使用到 dayjs 中
dayjs.extend(relativeTime)
// 得到相对时间
var time = dayjs().from(dayjs('1990'))
console.log(time) // 30 年以前
5.0 使用 dayjs 中的语言包
// 导入中文语言包
import 'dayjs/locale/zh-cn'
// 全局使用语言包
dayjs.locale('zh-cn')
思路:将 dayjs 封装为一个过滤器,在要修改时间的位置使用一下就可以了
复习过滤器:
定义过滤器:
Vue.filter('myfilter', function(value) { return xxx })
使用过滤器:
{{ msg | myfilter }}
步骤:
src
目录下创建一个 filter
文件夹filter
下面添加一个文件: myfilter.js
dayjs
将时间进行处理main.js
使用这个过滤器filter文件夹下的myfilter.js文件:
// 导入vue
import Vue from 'vue'
// 导入dayjs
import dayjs from 'dayjs'
// 导入相对时间插件
import relativeTime from 'dayjs/plugin/relativeTime'
// 导入语言包
import 'dayjs/locale/zh-cn'
// 拓展插件
dayjs.extend(relativeTime)
// 全局使用
dayjs.locale('zh-cn') // 全局使用
// 创建全局过滤器
Vue.filter('filterTime', function (value) {
// 处理时间
return dayjs().from(dayjs(value))
})
每篇文章下都有个小叉叉,点击叉叉时弹出这个more.vue组件
步骤:
index/com
中创建一个文件: more.vue
more.vue
中完成结构more.vue
显示和隐藏的属性点击不感兴趣之后,这篇文章就消失
步骤:
1.0 打开更多操作面板时:
3.0 给不感兴趣添加点击事件:
注意点:
User must be authorized
401
400
的错误
Invalid target article id
(无效的文章 id)400
错误的提示信息:无效的文章 id
错误的原因:
art_id
art_id
: 它是一个 Number 类型的数据,存储在服务器中art_id
会在请求数据时,返回给浏览器:
bug
:处理数字时是有范围的最大值最多为 9007199254740992
art_id
已经远远超过了 js 处理的最大数字总结:
原理:
如果要解决以上问题:可以使用第三方 json-bigint
步骤:
json-bigint
http.js
中导入 jsonbigint
JSON.parse
改为 jsonbigint.parse
transformResponse
(axios网站中请求配置Request Config目录下)中添加一个响应信息的转换代码jsonbig.parse
添加一个 try-catch
防止将来响应的数据为空时,代码报错步骤:
<template>
<div class="index">
<van-nav-bar title="主页" fixed />
<van-tabs v-model="active">
<van-tab v-for="(item,index) in channelList" :title="item.name" :key="index">
<van-pull-refresh v-model="item.isLoading" @refresh="onRefresh">
<van-list v-model="item.loading" :finished="item.finished" finished-text="没有更多了" @load="onLoad">
<van-cell v-for="(subitem, subindex) in item.articleList" :key="subindex">
<template #title>
<h4>{{subitem.title}}h4>
<van-grid :border="false" v-if="subitem.cover.type !== 0" :column-num="3" >
<van-grid-item v-for="(imgitem, imgindex) in subitem.cover.images" :key="imgindex">
<van-image lazy-load :src="imgitem" />
van-grid-item>
van-grid>
<div class="msgbox">
<div class="left">
<span>{{subitem.aut_name}}span>
<span>{{subitem.comm_count}} 评论span>
<span>{{ subitem.pubdate | filterTime }}span>
div>
<div class="right">
<van-icon @click="openMore(subitem)" name="cross" />
div>
div>
template>
van-cell>
van-list>
van-pull-refresh>
van-tab>
van-tabs>
<div class="process" @click="show=true">
<van-icon name="bars" />
div>
channel>
<more ref="moreRef" :artId="artId" @delArt="delArt">more>
div>
template>
<script>
// 导入子组件
import channel from './com/channel'
import more from './com/more'
// 导入网络请求方法
import { apiGetChannel } from '@/api/channel'
import { apiGetArticleList } from '@/api/article'
// 导入得到本地数据的方法
import { getLocal } from '@/utils/mylocal'
export default {
components: {
channel,
more
},
data () {
return {
// 存放用户频道的数据
channelList: [],
// 当前频道所在的下标
active: 0,
// 操作频道的子组件
show: false,
// 文章id
artId: 0,
// 作者id
autId: 0
}
},
mounted () {
this.getChannels()
},
methods: {
// 当list组件滚动到底部是会触发这个事件
async onLoad () {
console.log('到底')
// 1.0 得到当前选中的频道
const currentChannel = this.channelList[this.active]
// 2.0 得到当前有切换的频道的 id
const currentId = currentChannel.id
// 3.0 发送请求到服务器中,得到对应的文章数据
const res = await apiGetArticleList(currentId)
// console.log(res.data.data.results)
// 4.0 把获取到的res.data.data.results数组(文章列表)与当前所在的频道列表的articleList融合起来保存到当前选中的频道中
currentChannel.articleList = [...currentChannel.articleList, ...res.data.data.results]
// 5.0 将 list 组件的加载状态改为 false 因为有数据不要加载动画了
currentChannel.loading = false
// 6.0 判断当前频道的文章列表有没有数据,没有数据的话应该将list组件的数据状态设置为finished就会显示没有更多了
if (res.data.data.results.length === 0) {
currentChannel.finished = true
}
// console.log(currentChannel.articleList)
},
// 下拉刷新触发的方法
onRefresh () {
console.log('到头')
// 获得当前频道
const currentChannel = this.channelList[this.active]
// 清空当期频道的所有数据
currentChannel.articleList = []
currentChannel.isLoading = false
currentChannel.loading = true
currentChannel.finished = false
// 重新加载数据
this.onLoad()
},
// 获取用户频道
async getChannels () {
try {
// 1.0 判断用户有没有登录,有登录就有token
// 有token就直接发送请求得到用户频道
if (this.$store.state.userInfo.token) {
const res = await apiGetChannel()
// console.log(res)
this.channelList = res.data.data.channels
} else {
// 2.0 没有登录的话就判断本地有没有数据
const myList = getLocal('channelList')
if (myList) {
// 2.1 没有登录用户频道列表 就是本地那个列表
this.channelList = myList
} else {
// 2.2 本地没有数据就发送请求得到默认的数据而不是用户账户的数据
const res = await apiGetChannel()
this.channelList = res.data.data.channels
}
}
} catch (error) {
console.log('出错了')
}
// 添加额外属性
this.addOther()
},
// 添加额外属性方法
addOther () {
// 遍历每个用户频道列表,给每个用户频道列表添加一些自己的属性
this.channelList.forEach((item) => {
// item.articleList = []
this.$set(item, 'articleList', []) // 添加额外的文章数据
// item.loading = false
this.$set(item, 'loading', false) // 添加额外的上拉加载更多的状态属性
// item.isLoading = false
this.$set(item, 'isLoading', false) // 添加额外的下拉刷新属性
// item.finished = false
this.$set(item, 'finished', false) // 添加 list 的完结状态
})
},
// 当点击x时更多子子组件出现并且把子组件要的值传给他
openMore (item) {
this.$refs.moreRef.show = true
// 把被点击的文章id先复制给data中的artId 再传给more子组件
this.artId = item.art_id
// 把被点击的作者id传给more子组件
this.autId = item.aut_id
},
// 删掉文章的方法
delArt (artId) {
// 拿到当前的频道
const currentChannel = this.channelList[this.active]
// 遍历当前的频道中的文章列表,把传过来的被点击的文章id给删了
currentChannel.articleList.forEach((item, index) => {
// 如果传过来的被点击的文章的id与文章列表中的某篇文章相等
if (item.art_id === artId) {
// 就把文章列表那个文章删了
currentChannel.articleList.splice(index, 1)
}
})
}
}
}
script>
com文件夹下的more.vue更多操作面板代码
<template>
<div class="more">
<van-popup v-model="show" :style="{ width: '90%' }" @close="close">
<van-cell-group v-if="isReport===false">
<van-cell @click="dislike" icon="label-o" title="不感兴趣"/>
<van-cell @click="isReport=true" class="cellborder" icon="warning-o" title="反馈垃圾内容" is-link />
<van-cell @click="blacklist" icon="delete" title="拉黑作者" />
van-cell-group>
<van-cell-group v-else>
<van-cell icon="arrow-left" @click="isReport=false"/>
<van-cell @click="reportfn(item.id)" v-for="(item, index) in report" :key="index" :title="item.type"/>
van-cell-group>
van-popup>
div>
template>
<script>
import { apiDislike, apiReport } from '@/api/article'
import { apiblacklists } from '@/api/user'
export default {
// 接受父组件传来的被点击的文章id
props: ['artId'],
data () {
return {
show: false,
report: [
{ id: 0, type: '其他问题' },
{ id: 1, type: '标题夸张' },
{ id: 2, type: '低俗色情' },
{ id: 3, type: '错别字多' },
{ id: 4, type: '旧闻重复' },
{ id: 5, type: '广告软文' },
{ id: 6, type: '内容不实' },
{ id: 7, type: '涉嫌违法犯罪' },
{ id: 8, type: '侵权' }
],
isReport: false
}
},
methods: {
// 当点击不感兴趣时,要把当前频道的 被点击的这个文章从文章列表中删除
async dislike () {
// 如果用户登录了,就可以对不感兴趣的文章进行操作
if (this.$store.state.userInfo.token) {
// 要交给父组件去给文章列表被点击的这个文章从页面上删除
this.$emit('delArt', this.artId)
// 发送请求 将当前文章列表提交到服务器,将该文章标记为不感兴趣
await apiDislike(this.artId)
} else {
// 如果用户没登录
// 是不能对文章列表进行操作的
this.$toast('对不起,您未登录不能进行操作')
}
// 关闭面板
this.show = false
},
// 举报文章
async reportfn (typeId) {
try {
await apiReport({
artId: this.artId,
typeId: typeId
})
// 提示举报成功
this.$toast.success('举报成功')
} catch (err) {
this.$toast.success('数据异常')
}
// 关闭更多操作面板
this.show = false
// 把更多操作面板中的反馈信息弄成不显示,那下次再点击×是可以显示更多操作而不是反馈内容
this.isReport = false
},
// 拉黑作者
async blacklist () {
try {
// 登录了,发送请求拉黑作者
await apiblacklists(this.$parent.autId)
this.$toast.success('拉黑成功,请刷新')
} catch (err) {
// 如果用户没登录不能操作
this.$toast.fail(err.message)
}
// 关闭面板
this.show = false
},
// hidden (v) {
// // 返回更多操作中去
// this.isReport = false
// this.show = v
// }
// 当关闭弹出层时
close () {
// 反馈信息板块不展示
this.isReport = false
}
}
}
script>