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.vue
And 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; <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.vue
When 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.js
Add 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.vue
Component 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.js
Two 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.vue
In 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.js
Encapsulates 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.vue
In 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.vue
In 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.js
Encapsulates 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.vue
Component 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.js
To 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.vue
Component, 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.
- After pulling the data from the interface, update the view according to the IS_liking field
- 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.js
Add 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.vue
The 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