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('操作失败') } }