The main functions to be implemented are as follows:

Information list, TAB page switch, article report, channel management, article details, reading memory, focus on function, like function, comment function, reply to comment, search function, login function, personal center, edit information, xiaozhi students…

Today to achieve the main functions are: article details, focus on the function, praise function, comment function

1 Article details function

1.1 createviews/article/article.vueAnd write the following

<template> <div class="article-container"> <! <van-nav-bar fixed left-arrow @click-left="$router.back()" title=" $router.back "></van-nav-bar> <! -- / navigation bar --> <! Loading --> <van-loading class="article-loading" /> <! -- / loading loading --> <! <div class="detail"> <h3 class="title"> <div class="author"> <van-image round width="1rem" Height ="1rem" fit="fill" /> <div class="text"> <p class="name"> author </p> <p class="time">4 days ago </p> </div> <van-button round </van-button> </div> <div class="content"> </div> </div> <van-divider>END</van-divider> <div class="zan"> <van-button round size="small" hairline type="primary" plain < p style =" padding-bottom: 0px; padding-bottom: 0px; &nbsp; &nbsp; &nbsp; <van-button round size="small" hairline type="danger" plain icon="delete"> </van-button> </div> </div> <! </div> </template> <script> export default {name: 'ArticleIndex', data () {return {loading: Article: {}}}} </script> <style scoped lang='less'>. Article -container{position: absolute; left: 0; top: 0; overflow-y: scroll; width: 100%; height: 100%; } .article-loading { padding-top: 100px; text-align: center; } .error{ padding-top: 100px; text-align: center; } .detail { padding: 50px 10px; .title { font-size: 16px; } .zan{ text-align: center; } .author { padding: 10px 0; display: flex; .text { flex: 1; padding-left: 10px; The line - height: 1.3; .name { font-size: 14px; margin: 0; } .time { margin: 0; font-size: 12px; color: #999; } } } .content { font-size:14px; overflow: hidden; white-space: pre-wrap; word-break: break-all; /deep/ img{ max-width:100%; background: #f9f9f9; } } } </style>Copy the code

1.2 Route Configuration

{path: '/ article / : id', / / dynamic routing name: 'article' component: () = > import ('.. /views/article/article.vue') },Copy the code

1.3 Test Effect

1.4 Article details – Route jump

views/home/articleList.vueWhen you click on the article list item, pass the article ID to jump to the article details page

<van-cell
   v-for="(item,idx) in list"
   :key="idx"
   :title="item.title"
+  @click="$router.push('/article/' + item.art_id)"
>
Copy the code

1.5 Article details – Get data and display

1.5.1 Encapsulating Interfaces

inapi/article.jsAdd a method to the

@param {*} articleId */ export const getDetail = articleId => {return request({method: 'GET', url: 'v1_0/articles/' + articleId }) }Copy the code

1.5.2 Calling the Interface

inviews/article/article.vueComponent to get article details

created () {
    this.loadDetail()
  },
  methods: {
    async loadDetail () {
      try {
        this.loading = true
        const res = await getDetail(this.$route.params.id)
        this.article = res.data.data
        this.loading = false
      } catch (err) {
        this.loading = false
        console.log(err)
      }
    }
  }
Copy the code

1.5.3 Change the template and render the page

<! <van-nav-bar fixed left-arrow @click-left="$router.back()" :title="' article details -'+ article. Title "></van-nav-bar> <! -- / navigation bar --> <! Loading --> <van-loading v-if="loading" class="article-loading" /> <! -- / loading loading --> <! <h3 class="title">{{article. Title}}</h3> <div class="author"> <van-image round width="1rem" height="1rem" fit="fill" /> <div class="text"> <p class="name">{{article.aut_name}}</p> <p Class = "time" > {{article. For its pubdate | relativeTime}} < / p > < / div > < van - button round size = "small" type = "info" > + concern < / van - button >  </div> <div class="content"> <div v-html="article.content"></div> </div> <van-divider>END</van-divider> <div Class ="zan"> <van-button round size="small" hairline type="primary" plain icon="good-job-o"> </div> <! -- / article details -->Copy the code

Note:

  • The body of the article is an HTML format string that requires V-HTML to display properly
  • Relative time processing: Just use the global filter we defined earlier.

1.5.4 Viewing the Effect

2 Article operation – attention & close

  • If is_followed is false, the current loggeduser has not followed the author of this article.
  • If is_followed is true, the current loggeduser has followed the author of the article

2.1 Encapsulating Interfaces

inapi/user.jsTwo new methods are added to:

Export const follow = userId => {return request({url: '/v1_0/user/followings', // method: Data: {target: userId}})} export const unfollow = userId => {return request({url: '/v1_0/user/followings/' + userId, // method: 'DELETE' // method})}Copy the code

2.2 Calling an Interface

in\views\article\article.vueIn the template

<van-button round size="small" type="info" + @click="toggleFollow" + >{{article.is_followed ? }}</van-button>Copy the code
Async toggleFollow () {try {// Check the current state const isFollowed = this.article.is_followed const userId = this.article.aut_id Console. log(isFollowed) if (isFollowed) {// get await unfollow(userId)} else {await follow(userId)} // update view // 1. Whole resend request? Is_followed =! Is_followed =! IsFollowed this.$toast.success(' failed ')} Catch (err) {console.log(err) this.$toast.fail(' failed ')}}Copy the code

2.3 rendering

3 article operation – like & unlike

There is an attitude attribute in the article details retrieved from the back end to describe the user’s attitude towards the article, specifically: {-1: no attitude,0: dislike,1: like}.

If you’re doing a like, you’re changing attitude to 1. Unlikes, no attitude, that’s attiude -1.

Accordingly, there are two places to change on the view:

  • copy
  • icon

3.1 Encapsulation Interfaces

inapi/article.jsEncapsulates the data interface in

/**
 * 取消点赞
 * @param {*} id 文章编号
 */
export const deleteLike = id => {
  return request({
    method: 'DELETE',
    url: 'v1_0/article/likings/' + id
  })
}

/**
 * 添加点赞
 * @param {*} id 文章编号
 */
export const addLike = id => {
  return request({
    method: 'POST',
    url: 'v1_0/article/likings',
    data: {
      target: id
    }
  })
}

Copy the code

3.2 Calling Interfaces

Then, inviews/article/article.vueIn the component

<template> <van-button round size="small" hairline type="primary" plain :icon="article.attitude === 1 ? 'good-job': 'good-job-o'" @click="toggleLike">{{ article.attitude == 1 ? 'Unlike' : </van-button> </template> <script> import {addLike, deleteLike} from '@/ API /article' export default {//... methods: {async toggleLike () {try {// Check current state const attitude = this.article.attitude const artId = this.article.art_id if (attitude === 1) { await deleteLike(artId) this.article.attitude = -1 } else if (attitude === -1) { await addLike(artId) This.article. attitude = 1} this. toast.success(' successful ')} catch (err) {console.log(err) this. toast.fail(' failed ')}}  </script>Copy the code

3.3 rendering

4 404

4.1inviews/article/article.vueIn the component

Return {+ is404: false, // Whether loading: true, // control loading state article: {} // current article}Copy the code
async loadDetail () { try { this.is404 = false this.loading = true const {data:{data}} = await getDetail(this.$route.params.id) this.article = data this.loading = false } catch (err) { this.loading = false Console. dir(err) // How to determine the request is 404? if (err.response.status === 404) { this.is404 = true } } }Copy the code
<! Loading --> <van-loading v-if="loading" class="article-loading" /> <! <div class="error" v-if="is404"> <p> <van-button @click="$router.back()"> </van-button> < van - button @ click = "$router. Push ('/')" > back to home page < / van - button > < / div > <! - article details -- -- > < div class = "detail" v - else > / / omit -- -- -- -- -- < / div >Copy the code

rendering

5 Article Comments

5.1 Basic Layout

Article /comment.vue adds a component to complete the comment list function

<template> <div class="article-comments"> <! -- Comments list --> <van-list v-model="loading" :finished="finished" finished-text=" no more "@load="onLoad" > <van-cell v-for="item in list" :key="item.com_id" > <van-image slot="icon" round width="30" height="30" style="margin-right: 10px;" :src="item.aut_photo" /> <span style="color: #466b9d;" slot="title">{{item.aut_name}}</span> <div slot="label"> <p style="color: #363636;" >{{item.content}}</p> <p> <span style="margin-right: 10px;" >{{item.pubdate | relativeTime }}</span> </p> </div> <van-icon slot="right-icon" name="like-o" /> </van-cell> </van-list> <! -- Comment list --> <! -- Post comments --> <div :class="commentShow? 'art-cmt-container-1' : 'art-cmt-container-2'"> <! <div class="add-cmt-box van-hairline--top"> <van-icon name="arrow-left" size="24px" @click="$router.back()" /> <div class="ipt-cmt-div"> <div class="icon-box"> <van-badge content="10" Max ="99"> <van-icon name="comment-o" size="24px" /> </van-badge> <van-icon name="star-o" size="24px" /> <van-icon name="share-o" size="24px" /> </div> </div> <! <div class="cmt-box van-hairline--top" v-show="! Comments show "> <textarea placeholder=" commentText" ></textarea placeholder type="default" </van-button> </div> </div> <! </div> </template> <script> export default {name: 'ArticleComment', data () {return {commentText: ", commentShow: true, list: [], // Comment list Loading: false, // pull up loading more loading finished: false}}, methods: 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> <style scoped lang='less'> fixed; left: 0; bottom: 0; width: 100%; } // Leave space for the comments section. Van-list {margin-bottom: 45px; Padding-bottom: 46px; padding-bottom: 46px; padding-bottom: 46px; } .art-cmt-container-2 { padding-bottom: 80px; Add-cmt-box {position: fixed; bottom: 0; left: 0; width: 100%; box-sizing: border-box; background-color: white; display: flex; justify-content: space-between; align-items: center; height: 46px; line-height: 46px; padding-left: 10px; .ipt-cmt-div { flex: 1; border: 1px solid #efefef; border-radius: 15px; height: 30px; font-size: 12px; line-height: 30px; padding-left: 15px; margin-left: 10px; background-color: #f8f8f8; } .icon-box { width: 40%; display: flex; justify-content: space-evenly; line-height: 0; } } .child { width: 20px; height: 20px; background: #f2f3f5; CMT -box {position: fixed; bottom: 0; left: 0; width: 100%; height: 80px; display: flex; justify-content: space-between; align-items: center; font-size: 12px; padding-left: 10px; box-sizing: border-box; background-color: white; textarea { flex: 1; height: 66%; border: 1px solid #efefef; background-color: #f8f8f8; resize: none; border-radius: 6px; padding: 5px; } .van-button { height: 100%; border: none; } } </style>Copy the code

5.2 Register and Import

Load the registered article comment sub-component in the article details page SRC \views\article\article.vue:

import ArticleComment from './comment'

export default {
  ...
  components: {
    ArticleComment
  }
}
Copy the code
<div class="article-container"> ... <! <article-comment></article-comment> <! </div>Copy the code

rendering

5.3 Obtain and display article comment data

inapi/comment.jsEncapsulates the request method in

@param {*} articleId * @param {*} offset */ export const getComment = (articleId, offset) => { return request({ method: 'GET', url: '/v1_0/comments', params: { type: 'a', source: articleId, offset } }) }Copy the code

5.4 Loading and Obtaining Data

inviews/article/comment.vueComponent to load fetch data

// data () {return {total_count: 10, + offset: null, // Get the offset of the comment data from the comment id, if the comment id is not read from the first page}}Copy the code
import { getComments } from '@/api/comment' async onLoad () { try { // 1. Const {data:{data}} = await getComments(this.$route.params.id, this.offset) const arr = data.results // 2. Append to list this.list.push(... arr) // 3. loading <-false this.loading = false // 4. This. Finished =! Arr. length // 5. Update offset this.offset = data.last_id // 6. This. Total_count = data.total_count} Catch (error) {this.$toast(' get comment failed ') this.Copy the code

template

<van-cell v-for="item in list" :key="item.com_id" > <van-image slot="icon" round width="30" height="30" style="margin-right: 10px;" :src="item.aut_photo" /> <span style="color: #466b9d;" slot="title">{{item.aut_name}}</span> <div slot="label"> <p style="color: #363636;" >{{item.content}}</p> <p> <span style="margin-right: 10px;" > {{item. For its pubdate | relativeTime}} < / span > < van - button size = "mini" type = "default" > return < / van - button > < / p > < / div > < van - icon slot="right-icon" name="like-o" /> </van-cell>Copy the code

rendering

Post comments – Basic interaction

Use Boolean to control switching between two states

(1) Click to comment: From status 1 > Status 2

(2) When the input box loses focus, state 2 —-> State 1

6.1 the view

<! --> <div class="add-cmt-box van-hairline--top" v-show="commentShow"> <! <div class="cmt-box van-hairline--top" v-show="! commentShow">Copy the code

6.2 Adding Events and Attributes

<! -- Post comments --> <div :class="commentShow? 'art-cmt-container-1' : 'art-cmt-container-2'"> <! + <div class="add-cmt-box van-hairline--top" V-show ="commentShow"> <van-icon name="arrow-left" Size ="24px" @click="$router.back()" /> + <div class="ipt-cmt-div" @click="hShowCommentArea" class="icon-box"> <van-badge content="10" max="99"> <van-icon name="comment-o" size="24px" /> </van-badge> <van-icon name="star-o" size="24px" /> <van-icon name="share-o" size="24px" /> </div> </div> <! <div class="cmt-box van-hairline--top" v-show="! Comments show "> <textarea + ref=" TXT "+ @blur="hBlur" placeholder=" placeholder" v-model.trim="commentText" ></textarea> + <van-button type="default" @click="hAddComment"> </van-button> -- / Post a comment -->Copy the code

6.3 Executing Code

HAddComment () {alert('hAddComment')}, // This.$nextTick(() => {// this.mentshow = true //}) setTimeout(() => {// this.mentShow = true //}) setTimeout(() => { this.commentShow = true }) }, HShowCommentArea () {// State 2 will display this.mentshow = false // notify the view to update // $nextTick(callback function) // After updating the view, This.$nextTick(() => {this.$refs.txt.focus()})}Copy the code

6.4 Post comments on articles

inapi/comment.jsTo add the encapsulated data interface

Import request from '@/utils/request.js' /** * Add comment * @param {*} articleId * @param {*} content */ export const addComment = (articleId, content) => { return request({ method: 'POST', url: 'v1_0/comments', data: { target: articleId, content } }) }Copy the code

incomment.vueComponent, hAddComment

Async hAddComment () {if (! this.commentText) { return } try { const {data:{data}} = await addComment(this.$route.params.id, This.list. unshift(data.new_obj) // Update page} this.list.unshift(data.new_obj) // update page} Catch (err) {console.log(err) this.$toast.fail(' failed to add comment ')}},Copy the code

6.5 Implementing Comment Posting ===> Scroll bar to scroll to the comment Posting position

Add a div with id=”scrollTh” at the top of comment.vue

<div id="scrollTh"></div>
Copy the code
Async hAddComment () {if (! this.commentText) { return } try { const res = await addComment(this.$route.params.id, This.list.unshift (res.data.data.new_obj) // this.list.unshift(res.data.data.new_obj) // this.list.unshift(res.data.data.new_obj $this.$el.querySelector('#scrollTh'). ScrollIntoView ({// top: 0, // el: '#scrollTh', + behavior: 'smooth', // Smooth transition + block: 'start' // Top border flush with window top. Default +}) // Update the page} catch (err) {console.log(err) this.$toasts. Fail (' failed to add comment ')}},Copy the code

7 comments on articles – thumbs up

In the data retrieved from the back-end interface, a special field is_liking indicates whether the current user likes the current comment.

  1. After pulling the data from the interface, update the view according to the IS_liking field
  2. After the user clicks, the interface is called to modify the value of the IS_liking field on the server and to update the view.

7.1 Encapsulating Interfaces

inapi/comment.jsAdd two methods to

@param {*} commentId commentId */ export const addCommentLike = commentId => {return request({method: 'POST', url: 'v1_0/comment/likings', data: { target: CommentId}})} @param {*} commentId commentId */ export const deleteCommentLike = commentId => {return request({ method: 'DELETE', url: 'v1_0/comment/likings/' + commentId }) }Copy the code

7.2 Modifying a View

inviews/article/comment.vueThe component:

<van-icon
       slot="right-icon"
       :name="item.is_liking ? 'like' : 'like-o'"
       @click="hToggleLike(item)"
/>
Copy the code

7.3 Specific implementation code

<script> import { getComments, addComment, + addCommentLike, + deleteCommentLike } from '@/api/comment' export default { methods: Async hToggleLike (item) {try {const isLiking = item.is_liking const commentId = item.com_id if {await deleteCommentLike(commentId)} else {await addCommentLike(commentId)} this.$toast. Success (' liking ') Is_liking =! {console.log(err) this.$toast.fail(' operation failed ')}}}} </script>Copy the code

rendering