仿今日头条项目——文章详情

1.创建组件并配置路由

1.创建 views/article/index.vue 组件

<template>
  <div class="article-container">文章详情</div>
</template>

<script>
export default {
  name: 'ArticleIndex',
  components: {},
  props: {
    articleId: {
      type: [Number, String],
      required: true
    }
  },
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>

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

2、然后将该页面配置到根级路由
因为篇文章跳转路径不同,因此使用动态路由:       path: '/article/:articleId',

并用props传参!!!!(props解耦)

{
  path: '/article/:articleId',
  name: 'article',
  component: () => import('@/views/article'),
  // 将路由动态参数映射到组件的 props 中,更推荐这种做法
  props: true
}

3.在首页的文章cell区域添加to属性实现路径跳转

<van-cell
    class="article-item"
    :to="{
      //根据路由名称进行跳转
      name: 'article',
      //传递路由动态参数
      params: {
        articleId: article.art_id
      }
    }"
  >
...
</van-cell>

其他两种to属性写法(拼接):

1. :to="'/article/' + article.art_id" 

2. :to="`/article/${article.art_id}`" 


2. 页面布局


3.请求获取文章数据

1.找到数据接口


2.封装请求方法

/**
 * 根据 id 获取指定文章
 */
export const getArticleById = articleId => {
  return request({
    method: 'GET',
    url: `/app/v1_0/articles/${articleId}`
  })
}

3.请求获取数据

 import { getArticleById } from '@/api/article'

export default {
  name: 'ArticlePage',
  components: {},
  props: {
    articleId: {
      type: String,
      required: true
    }
  },
  data () {
    return {
      article: {} // 文章详情
    }
  },
  computed: {},
  watch: {},
  created () {
    this.loadArticle()
  },
  mounted () {},
  methods: {
    async loadArticle () {
      try {
        const { data } = await getArticleById(this.articleId)
        this.article = data.data
      } catch (err) {
        console.log(err)
      }
    }
  }
}

bug:404报错(大整数)

因为我们请求发送的文章 ID (article.art_id)不正确

JavaScript 能够准确表示的整数范围在`-2^53`到`2^53`之间(不含两个端点),超过这个范围,无法精确表示这个值,这使得 JavaScript 不适合进行科学和金融方面的精确计算。

解决:json-bigint(第三方包)

1)下载

2)使用

import axios from 'axios'

import jsonBig from 'json-bigint'

var json = '{ "value" : 9223372036854775807, "v2": 123 }'

console.log(jsonBig.parse(json))

const request = axios.create({
  baseURL: 'http://ttapi.research.itcast.cn/', // 接口基础路径

  // transformResponse 允许自定义原始的响应数据(字符串)
  transformResponse: [function (data) {
    try {
      // 如果转换成功则返回转换的数据结果
      return jsonBig.parse(data)
    } catch (err) {
      // 如果转换失败,则包装为统一数据格式并返回
      return {
        data
      }
    }
  }]
})

export default request

4.模板绑定


4.处理内容加载状态 

- 加载中,显示 loading
- 加载成功,显示文章详情
- 加载失败,显示错误提示
  - 如果 404,提示资源不存在
  - 其它的,提示加载失败,用户可以点击重试重新加载


5.正文样式 

文章正文包括各种数据:段落、标题、列表、链接、图片、视频等资源。
1.github-markdown-css:样式文件下载到项目中
2.配置不要转换样式文件中的字号


6.图片点击预览 

 

1、从文章内容中获取到所有的 img DOM 节点

2、获取文章内容中所有的图片地址

3、遍历所有 img 节点,给每个节点注册点击事件

4、在 img 点击事件处理函数中,调用 ImagePreview 预览

bug:在图片加载成功的时候获取节点(this.$refs['article-content'])不能马上显示,因为数据驱动视图这件事不是立即的

解决:settimeout(function(){},0)

<!-- 文章内容 -->
        <div
          class="article-content markdown-body"
          v-html="article.content"
          ref="article-content"
        ></div>
 async loadArticle() {
      // 展示 loading 加载中
      this.loading = true
      try {
        const { data } = await getArticleById(this.articleId)

        // if (Math.random() > 0.5) {
        //   JSON.parse('dsankljdnskaljndlkjsa')
        // }

        // 数据驱动视图这件事儿不是立即的
        this.article = data.data

        // 初始化图片点击预览
        // console.log(this.$refs['article-content'])
        setTimeout(() => {
          this.previewImage()
        }, 0)

        // 请求成功,关闭 loading
        // this.loading = false
      } catch (err) {
        if (err.response && err.response.status === 404) {
          this.errStatus = 404
        }
        // this.loading = false
        // console.log('获取数据失败', err)
      }

      // 无论成功还是失败,都需要关闭 loading
      this.loading = false
    },

    previewImage() {
      // 得到所有的 img 节点
      const articleContent = this.$refs['article-content']
      const imgs = articleContent.querySelectorAll('img')

      // 获取所有 img 地址
      const images = []
      imgs.forEach((img, index) => {
        images.push(img.src)

        // 给每个 img 注册点击事件,在处理函数中调用预览
        img.onclick = () => {
          ImagePreview({
            // 预览的图片地址数组
            images,
            // 起始位置,从 0 开始
            startPosition: index
          })
        }
      })
    },

 


7.关注用户

- 给按钮注册点击事件
- 在事件处理函数中
  - 如果已关注,则取消关注
  - 如果没有关注,则添加关注

          <van-button
            v-if="article.is_followed"
            class="follow-btn"
            round
            size="small"
            :loading="followLoading"
            @click="onFollow"
          >已关注</van-button>
          <van-button
            v-else
            class="follow-btn"
            type="info"
            color="#3296fa"
            round
            size="small"
            icon="plus"
            :loading="followLoading"
            @click="onFollow"
          >关注</van-button>

1.找到数据接口


2.在 `api/user.js` 中添加封装请求方法

/**
 * 添加关注
 */
export const addFollow = userId => {
  return request({
    method: 'POST',
    url: '/app/v1_0/user/followings',
    data: {
      target: userId
    }
  })
}

/**
 * 取消关注
 */
export const deleteFollow = userId => {
  return request({
    method: 'DELETE',
    url: `/app/v1_0/user/followings/${userId}`
  })
}

3.请求调用并更新视图

import { addFollow, deleteFollow } from '@/api/user'

...


async onFollow () {
  // 开启按钮的 loading 状态
  this.isFollowLoading = true

  try {
    // 如果已关注,则取消关注
    const authorId = this.article.aut_id
    if (this.article.is_followed) {
      await deleteFollow(authorId)
    } else {
      // 否则添加关注
      await addFollow(authorId)
    }

    // 更新视图
    this.article.is_followed = !this.article.is_followed
  } catch (err) {
    console.log(err)
    this.$toast.fail('操作失败')
  }

  // 关闭按钮的 loading 状态
  this.isFollowLoading = false
}

因多处使用,考虑封装组件!!

优化:模板中的 $event 是事件参数,当我们传递给子组件的数据既要使用还要修改。

传递:props                            :is-followed="article.is_followed"

修改:自定义事件                  @update-is_followed="article.is_followed = $event"

简写方式:在组件上使用 v-model

value="article.is_followed"

@input="article.is_followed = $event"

(如果需要修改 v-model 的规则名称,可以通过子组件的 model 属性来配置修改)

(一个组件上只能使用一次 v-model,如果有多个数据需要实现类似于 v-model 的效果,可以使用属性的 .sync 修饰符)


8.文章收藏

1、在 `api/article.js` 添加封装数据接口

2、给收藏按钮注册点击事件

3、处理函数

async onCollect () {
  // 这里 loading 不仅仅是为了交互提示,更重要的是请求期间禁用背景点击功能,防止用户不断的操作界面发出请求
  this.$toast.loading({
    duration: 0, // 持续展示 toast
    message: '操作中...',
    forbidClick: true // 是否禁止背景点击
  })

  try {
    // 如果已收藏,则取消收藏
    if (this.article.is_collected) {
      await deleteCollect(this.articleId)
      // this.article.is_collected = false
      this.$toast.success('取消收藏')
    } else {
      // 添加收藏
      await addCollect(this.articleId)
      // this.article.is_collected = true
      this.$toast.success('收藏成功')
    }
    this.article.is_collected = !this.article.is_collected
  } catch (err) {
    console.log(err)
    this.$toast.fail('操作失败')
  }
}

9.文章点赞

1、添加封装数据接口

2、给点赞按钮注册点击事件

3、处理函数
 

async onLike () {
  // 两个作用:1、交互提示 2、防止网络慢用户连续不断的点击按钮请求
  this.$toast.loading({
    duration: 0, // 持续展示 toast
    message: '操作中...',
    forbidClick: true // 是否禁止背景点击
  })

  try {
    // 如果已经点赞,则取消点赞
    if (this.article.attitude === 1) {
      await deleteLike(this.articleId)
      this.article.attitude = -1
      this.$toast.success('取消点赞')
    } else {
      // 否则添加点赞
      await addLike(this.articleId)
      this.article.attitude = 1
      this.$toast.success('点赞成功')
    }
  } catch (err) {
    console.log(err)
    this.$toast.fail('操作失败')
  }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值