使用Vue开发项目(黑马头条项目)--第四天

287 阅读2分钟

需要实现的主要功能如下:

资讯列表、标签页切换,文章举报,频道管理、文章详情、阅读记忆,关注功能、点赞功能、评论功能、回复评论、搜索功能、登录功能、个人中心、编辑资料、小智同学 ...

首先回顾一下:昨天我们实现了二级路由,封装了时间过滤器,图片懒加载等功能

今天我们要实现的功能主要是:文章的不感兴趣、举报功能

1 token功能的优化

1.1 封装token(由于之前token存储在vuex中,导致每次刷新页面都会清空token,还需要重新登录)

在utils中新建页面storage.js

// 封装对token的三个操作(存储,删除,获取)

// 消除魔法字符串
const TOKEN = 'token'

// 存储
export const saveToken = (tokenObj) => {
  localStorage.setItem(TOKEN, JSON.stringify(tokenObj))
}

// 获取
export const getToken = () => {
  return JSON.parse(localStorage.getItem(TOKEN))
}

// 删除
export const delToken = () => {
  localStorage.removeItem(TOKEN)
}

1.2 在src/store/index.js中加入以下代码:

+ import { saveToken, getToken } from '@/utils/storage'
state: {
    // 优先从localstroage中取出
+    tokenInfo: getToken() || {}
  },
 actions: {
    // context对象会自动传入,它与store示例具有相同的方法和context对象action的名字: function(context, 载荷) {// 1. 发异步请求, 请求数据// 2. commit调用mutation来修改/保存数据// context.commit('mutation名', 载荷)}
    async setToken (context, userInfo) {
      try {
        // console.log(2)
        const { data: { data } } = await login(userInfo)
        // console.log(data)
        // console.log(3)
        context.commit('getToken', data)
        // 将token存储到浏览器本地
+       saveToken(data)
        // this.$toast.success('登录成功')
      } catch (err) {
        // 将错误抛给调用这个actions的函数
        throw new Error(err)
      }
    }
  },

便可解决每次刷新页面后token被清空的问题

2.开始今天功能

2.1 目标功能展示

更多操作.gif

2.2 在articleList.vue中加入一个×图标

<!-- 文字区域 -->
            <div class="meta">
              <span>{{ item.aut_name }}</span>
              <span>{{ item.comm_count }}评论</span>
              <span>{{ item.pubdate | relativeTime }}</span>
             
              <span class="close">
                <van-icon name="cross" />
              </span>
            </div>

给其加入样式,让它显示在右侧

.meta {
  span {
    margin-right: 10px;
  }
  display: flex;
  // 让它在最右边
  .close {
    // 它的父级容器是flex的,给当前元素设置左外边距为auto,会让这个元素在父级容器
    // 的最右边
    margin-left: auto;
  }
}

2.2 这个图标只有在用户登录时才可以看到,所以要加入判定

判定在vuex的state的变量中token是否存在,若存在则显示,反之不显示

<span class="close">
   <van-icon name="cross" v-if="$store.state.tokenInfo.token"/>
</span>

2.3 给关闭按钮创建点击事件

<span class="close">
   <van-icon name="cross" 
   v-if="$store.state.tokenInfo.token"
   @click="hActionMore(item.art_id)"
   />
</span>
methods:{
    data(){
        return{
            isShowMoreAction:false, //默认不显示弹出层
            articleId:null,
        }
    }
    MoreAction (id) {
          // 点击显示弹出层
          this.isShowMoreAction = true
          // 保存当前id
          this.articleId = id
        },
}

3 使用弹层组件(popup组件)

因为弹出层之中的内容较为复杂,我们将其拆出为一个新的组件

3.1在src/views/home/新建moreAction.vue文件

<template>
  <div class="more-action">
    <!-- 状态一 -->
    <van-cell-group>
      <van-cell>不感兴趣</van-cell>
      <van-cell is-link>反馈垃圾内容</van-cell>
      <van-cell>拉黑作者</van-cell>
    </van-cell-group>
    <!-- 状态二 反馈-->
    <van-cell-group>
      <van-cell icon="arrow-left">返回</van-cell>
      <van-cell>侵权</van-cell>
      <van-cell>色情</van-cell>
      <van-cell>暴力</van-cell>
      <van-cell>低俗</van-cell>
      <van-cell>不适</van-cell>
      <van-cell>错误</van-cell>
      <van-cell>其他</van-cell>
    </van-cell-group>
  </div>
</template>

<script>
export default {
  name: 'MoreAction',
}
</script>

3.2 在articleList.vue中和其建立父子组件关系

+ import MoreAction from './moreAction'
+ components: {
    MoreAction
  },

放在van-pull-refresh标签的外面(放在循环的外面)

 <!-- 弹出层 -->
    <van-popup v-model="isShowMoreAction" :style="{ width: '80%' }">
      <MoreAction></MoreAction>
    </van-popup>

3.3 查看调试工具

image.png

两个弹出层一起显示了,我们让其分开显示两个只能显示一个

<template>
  <div class="more-action">
    <!-- 状态一 -->
+    <van-cell-group v-if="!isReport">
      <van-cell>不感兴趣</van-cell>
+      <van-cell is-link @click="isReport=true">举报垃圾内容</van-cell>
      <van-cell>拉黑作者</van-cell>
    </van-cell-group>
    <!-- 状态二 举报-->
    <van-cell-group v-else>
+      <van-cell icon="arrow-left" @click="isReport=false">返回</van-cell>
      // --- 省略其他
    </van-cell-group>
  </div>
</template>

<script>
export default {
  name: 'MoreAction',
  data () {
    return {
+      isReport: false // 是否显示 举报
    }
  }
}
</script>

4 实现不感兴趣功能

4.1思路

  1. 去调用接口。让服务器之后不要再推类似的文章给我了。

  2. 关闭弹层

  3. 把我点击的文章删除掉。

在当前articleList组件中删除文章

4.2 利用子传父原理(自定义事件)实现

在父组件articleList.vue中监听事件,及回调

    <van-popup v-model="isShowMoreAction" :style="{ width: '80%' }">
      <moreAction 
+      @un-like="hUnlike"
      ></moreAction>
    </van-popup>

回调函数

async hUnlike () {
  try {
    // 1. 调用接口,传文章编号
    
    // 2. 关闭弹层
    this.isShowMoreAction = false
    // 3. 删除文章

    this.$toast.success('操作成功')
  } catch (err) {
    this.$toast.fail('操作失败')
  }
}

在子组件moreAction.vue中抛出事件

<!-- 直接抛出事件,具体功能由父组件index.vue去做 -->
<van-cell @click="hUnlike">不感兴趣</van-cell>

methods: {
    // 用户点击不感兴趣
    // 要做三件事,但是,这三件事都在父组件articleList中去做
    hUnlike () {
      this.$emit('un-like')
    }
  }

4.3 封装请求函数在上步回调函数中调用

src/api/article.js中添加一个方法

export const unLike = (articleId) => {
  return request({
    url: 'v1_0/article/dislikes',
    method: 'POST',
    data: {
      target: articleId
    }
  })
}

调用

import { unLike } from '@/api/article.js'
async hUnlike () {
      // 1. 发请求(通知后端,以后不要推荐这类文章给我)
      try {
        await unLike(this.articleId)
        
        this.$toast.success('操作成功')
        // 2. 删除文章:根据文章的编号在list中删除
     
      	// 3. 关闭弹层
      	this.isShowMoreAction = false
      } catch (err) {
        console.log(err)
        this.$toast.fail('操作失败')
      }
      
    }

4.4 删除文章

之后的功能可能还会使用到这个功能,所以给他封装到局部methods中,使用时调用即可

delAirticle (articleId) {
      const idx = this.list.findIndex(item => { return item.art_id === articleId })
      if (idx !== -1) { this.list.splice(idx, 1) }
    },

调用(在上步删除文章中加入下面代码)

this.delAirticle()

5 举报文章

5.1 举报类型封装

按照应给后端传递的数据格式内容较为复杂,我们依旧是将其封装到一个新页面中, src/constant/新建reports.js文件

// 以模块的方式导出 举报文章 时,后端接口约定的举报类型
const reports = [
  {
    value: 0,
    label: '其它问题'
  },
  {
    value: 1,
    label: '标题夸张'
  },
  {
    value: 2,
    label: '低俗色情'
  },
  {
    value: 3,
    label: '错别字多'
  },
  {
    value: 4,
    label: '旧闻重复'
  },
  {
    value: 5,
    label: '广告软文'
  },
  {
    value: 6,
    label: '内容不实'
  },
  {
    value: 7,
    label: '涉嫌违法犯罪'
  },
  {
    value: 8,
    label: '侵权'
  }
]
export default reports

5.2 对页面进行渲染

在moreAction.vue中

// 引用提前定义好的常量数据
+ import reports from '@/constant/reports.js'
export default {
  name: 'MoreAction',
  data () {
    return {
+     reports: reports,
      isReport: false // 是否是处于状态二:反馈
    }
  },
    <van-cell-group v-else>
      <van-cell icon="arrow-left" @click="isReport=false">返回</van-cell>
			<van-cell
        v-for="report in reports"
        :key="report.value"
      >
        {{report.label}}
      </van-cell>
    </van-cell-group>

实现效果

image.png

5.3 实现举报文章功能

依旧重复上次子传父功能(自定义事件) 在home/articleList.vue中监听事件。

<more-action
        ref="moreAction"
+       @report="hReport"
        @un-like="hUnlike"
      ></more-action>
      
      // 用户点击了举报中的某一个
    hReport (reportTypeId) {
      console.log('收到的举报类型是', reportTypeId)
   
    }

在子组件汇总抛出事件

<van-cell
        v-for="report in reports"
        :key="report.value"
        @click="$emit('report', report.value)"
      >
        {{report.label}}
      </van-cell>

5.4 封装接口

在src/api/article.js中补充一个方法

export const report = (articleId, reportType) => {
  return request({
    method: 'POST',
    url: 'v1_0/article/reports',
    data: {
      target: articleId,
      type: reportType
    }
  })
}

在父组件src/home/articleList.vue中,引入api(删除文章功能直接调用即可)

import { dislike, report } from '@/api/article.js'

async hReport (reportTypeId) {
  try {
    // 1. 发请求,举报
    await report(this.articleId, reportTypeId)
    // 2. 删除文章:根据文章的编号在list中删除
    this.delArticle(this.articleId)
    // 3. 关闭弹层
    this.isShowMoreAction = false

    this.$toast.success('操作成功')
  } catch (err) {
    console.log(err)
    this.$toast.fail('操作失败')
  }
}

5.5 BUG(当在举报页面关闭,再次点开还是举报页面的问题)

点开显示举报弹窗是由moreAction中的isReport控制的,我们只需要在一打开就把isReport设置为false即可

这需要用到ref属性(父组件操控子组件的数据)

将子组件中的isreport设置为false

如何在父组件中修改子组件的数据?

(1)在父组件中获取组件的引用 (ref 用来获取,this.$refs.XXXX 用来找到组件)

在组件上添加ref, 这个组件就被自动收集到this.$refs中。

(2)为所欲为 第一次点击会报错isReport is undefined, 因为第一次点击时 弹出层还未创建,所以会报错 判断当第一次它创建完毕之后再次更改即可解决

加入以下代码

 <more-action
+        ref="moreAction"
        @un-like="hUnlike"
      ></more-action>
 hMoreAction (id) {
      // 1. 显示 更多操作
      this.isShowMoreAction = true
      // 2. 记下当前被操作的文章的编号
      this.articleId = id
      // 用ref获取子组件的引用,然后通过引用来修改子组件中的数据
      // 第一次打开弹层时,子组件并没有被创建出来,所以这里要先判断一下
+     if (this.$refs.moreAction) {
+       this.$refs.moreAction.isReport = false
+     }
    }