05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)

主页数据

01 - 完成数据的结构 & 样式

  • 文章数组的组成:
    • 文章的标题
    • 文章的图片
      • 可以使用 vant 中的组件:
    • 文章的作者 & 评论数 & 发布日期 更多操作按钮
      05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第1张图片
      05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第2张图片

02 - 动态渲染数据

打印当前频道的文章列表可以看到以下数据,然后再渲染到页面上

了解文章数据源中的数据:

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:文章的标题

03 - 实现图片懒加载

组件image图片: 链接: link.

为什么要实现懒加载:

1.0 因为网速的原因:如果一次性将图片全部加载出来,网络差一点,页面打开非常慢

2.0 因为是移动端:如果加载出来的图片用户看不到,就没有意义,浪费流量。

使用 lazy-load 指令来实现懒加载

步骤:

  • 1.0 给图片设置一个指令 lazy-load
    05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第3张图片

  • 2.0 向 vue 中注册这个 lazy-load 指令

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

04 - dayjs 的使用

dayjs官网: link.

将具体时间改为相对时间(可以使用一个第三方包: dayjs (与 Moment.js 的用户基本一致))

dayjs:

  • 作用:与 moment.js 一样,可以用来进行时间处理

  • 特点:

    • 用法与 moment.js 一样
    • 大小比 moment.js 要小得多,只有 2 kb
    • dayjs 的 API 设置与 moment.js 完全一样
  • 使用步骤:

    • 1.0 安装第三方包:npm install dayjs --save

    • 2.0 导入 dayjsimport 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')
      

05 - 修改时间

05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第4张图片

思路:将 dayjs 封装为一个过滤器,在要修改时间的位置使用一下就可以了

复习过滤器:

定义过滤器: Vue.filter('myfilter', function(value) { return xxx })

使用过滤器:{{ msg | myfilter }}

步骤:

  • 1.0 在 src 目录下创建一个 filter 文件夹
  • 2.0 在 filter 下面添加一个文件: myfilter.js
  • 3.0 在文件中定义一个过滤器:
    • 使用 dayjs 将时间进行处理
  • 4.0 在 main.js 使用这个过滤器
  • 5.0 在需要的地方使用这个过滤器

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))
})

更多操作

01 - 将面板封装为一个组件

05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第5张图片

  • 每篇文章下都有个小叉叉,点击叉叉时弹出这个more.vue组件

  • 步骤:

    • 1.0 在 index/com 中创建一个文件: more.vue
    • 2.0 在 more.vue 中完成结构
    • 3.0 给更多操作按钮添加一个点击事件:
    • 在事件中修改控制 more.vue 显示和隐藏的属性

02 - 不感兴趣

  • 点击不感兴趣之后,这篇文章就消失

  • 步骤:

  • 1.0 打开更多操作面板时:

    • 记录当前点击的文章数据的 id
      在这里插入图片描述
      在这里插入图片描述
    • 将文章的 id 传入到更多面板中
      在这里插入图片描述
  • 2.0 在更多操作面板中接收文章的 id
    在这里插入图片描述

  • 3.0 给不感兴趣添加点击事件:

    • 将 id 对应的文章从页面上删除
      05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第6张图片
      • 将 id 传回到首页中
      • 在首页中根据 id 将文章对应的数据进行删除
        05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第7张图片
    • 将 id 对应的文章提交到服务器标记为 不喜欢
      • 请求服务器接口:对文章不喜欢

注意点:

  • 1.0 如果要对文章不感兴趣,必须用户已经登录:
    • 如果没有登录报的错误是: User must be authorized
    • 错误的状态: 401
  • 2.0 登录之后还是会报一个 400 的错误
    • 报错信息是: Invalid target article id (无效的文章 id)
    • 错误的状态: 400

03 - 分析错误

错误的提示信息:无效的文章 id

错误的原因:

  • 由于黑马头条中的新闻信息非常多
  • 在存储这些信息时,会给这些信息设置一个唯一的标识的 art_id
  • art_id: 它是一个 Number 类型的数据,存储在服务器中
  • 将来这个 art_id 会在请求数据时,返回给浏览器:
    • 浏览器中有一个 bug:处理数字时是有范围的最大值最多为 9007199254740992
    • 由于新闻数据非常多:art_id 已经远远超过了 js 处理的最大数字
    • js 在遇到比它处理范围要大的数字时,会偷懒:
      • 丢失精度

总结:

  • 由于新闻的 id 太长,超过了浏览器中 js 的处理范围,造成了 id 精度的丢失。页面最终报错

04 - 错误产生的原理

原理:

  • 1.0 服务器返回的数据中的 id 超过 js 的处理范围
  • 2.0 在浏览器中接收到这些数据(JSON 格式的字符串)之后,会将数据进行处理 (JSON.parse)
  • 3.0 JSON.parse 在处理时,会对超过范围的数据进行(造成精度的丢失)

05 - 解决数字超过 js 的处理范围

如果要解决以上问题:可以使用第三方 json-bigint

步骤:

  • 1.0 下载 json-bigint
  • 2.0 在 http.js 中导入 jsonbigint
  • 3.0 在接收响应信息的位置将转换的代码从 JSON.parse 改为 jsonbigint.parse
    • transformResponse (axios网站中请求配置Request Config目录下)中添加一个响应信息的转换代码
  • 4.0 给 jsonbig.parse 添加一个 try-catch 防止将来响应的数据为空时,代码报错
    05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第8张图片

06- 反馈垃圾内容

步骤:

  • 1.0 在更多操作面板中添加另一个 van-cell-group
  • 2.0 给反馈垃圾内容添加一个点击事件
    • 在事件中将举报详情显示出来: isReport 设置为 true
  • 3.0 在举报详情中添加一个向左的箭头
    • 给箭头添加一个点击事件:在事件中将 isReport 设置为 false
      05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第9张图片05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第10张图片
  • 4.0 给每个举报选项添加一个点击事件
    • 得到当前要举报的文章的 id
    • 得到当前要举报的类型 id
    • 发送请求到服务器举报文章
    • 添加举报的提示框
    • 将面板切换到不感兴趣结构
    • 关闭面板
      05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第11张图片
      05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第12张图片
      05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第13张图片

05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第14张图片

07 - 拉黑作者

  • 先在api里定义一个接口方法
    05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第15张图片
  • 在更多操作子组件中,点击拉黑作者执行blacklist方法
    05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第16张图片
  • 定义拉黑作者的方法
    05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第17张图片
  • 拉黑作者的方法blacklist中要用到作者id,可以通过下面(父组件index)截图方式然后再利用this$parent拿到
    05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第18张图片
    详细代码(样式没复制):
    主页index组件:
<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>

05-vue移动端项目(主页index文章列表数据的渲染,插件弄图片懒加载,dayjs的相对时间(比momentjs体积小用法一样)的使用,全局过滤器,解决js处理数字最大范围json-bigint)_第19张图片

你可能感兴趣的:(笔记)