# 五、首页—频道编辑

# 1、处理页面弹出层

Vant 中内置了 Popup 弹出层 (opens new window) 组件。

1、在views/home/index.vue里面 data中添加一个数据变量

data () {
  return {
    ...
    isChannelEditShow: true // 这里我们先设置为 true 就能看到弹窗的页面了
  }
}
1
2
3
4
5
6

2、在views/home/index.vue里面添加弹出层组件

<!-- 频道编辑 -->
<van-popup
  class="edit-channel-popup"
  v-model="isChannelEditShow"
  position="bottom"
  :style="{ height: '100%' }"
  closeable
  close-icon-position="top-left"
>内容</van-popup>
<!-- /频道编辑 -->
1
2
3
4
5
6
7
8
9
10
.edit-channel-popup {
  box-sizing: border-box;
}
1
2
3

 



<!--汉堡按钮绑定事件-->
<div slot="nav-right" class="hamburger-btn" @click="isChannelEditShow=true">
    <i class="toutiao toutiao-gengduo"></i>
</div>
1
2
3
4

测试查看结果。

# 2、创建频道编辑组件

1、创建 views/home/components/channel-edit.vue

<template>
  <div class="channel-edit">频道编辑</div>
</template>

<script>
export default {
  name: 'ChannelEdit',
  components: {},
  props: {},
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

2、在首页home/index.vue中导入、注册、使用

import ChannelEdit from './components/channel-edit'
1
export default {
  ...
  components: {
    ...
    ChannelEdit
  }
}
1
2
3
4
5
6
7
<!-- 频道编辑 -->
<van-popup
  class="edit-channel-popup"          
  v-model="isChannelEditShow"
  position="bottom"
  closeable
  close-icon-position="top-left"
  :style="{ height: '100%' }"
>
 <channel-edit />  <!-- 使用组件 -->
</van-popup>
<!-- /频道编辑 -->
1
2
3
4
5
6
7
8
9
10
11
12

# 3、页面布局

<template>
  <div class="channel-edit">
    <van-cell :border="false">
      <div slot="title" class="title-text">我的频道</div>
      <van-button
        class="edit-btn"
        type="danger"
        plain
        round
        size="mini"
      >完成/编辑</van-button>
    </van-cell>
    <van-grid class="my-grid" :gutter="10">
      <van-grid-item
        class="grid-item" 
      >
        <van-icon
          slot="icon"
          name="clear"
        ></van-icon>
        <span
          class="text"
          slot="text"
        >名称</span>
      </van-grid-item>
    </van-grid>

    <!-- 频道推荐 -->
    <van-cell :border="false">
      <div slot="title" class="title-text">频道推荐</div>
    </van-cell>
    <van-grid class="recommend-grid" :gutter="10">
      <van-grid-item
        class="grid-item"
        icon="plus"
        text="名称"
      />
    </van-grid>
    <!-- /频道推荐 -->
  </div>
</template>

<script>
export default {
  name: 'ChannelEdit',
  components: {},
  props: {},
  data () {
    return {}
  },
  computed: {},
  watch: {},
  created () {},
  mounted () {},
  methods: {}
}
</script>

<style scoped lang="less">
.channel-edit {
  padding: 85px 0;
  .title-text {
    font-size: 32px;
    color: #333333;
  }

  .edit-btn {
    width: 104px;
    height: 48px;
    font-size: 26px;
    color: #f85959;
    border: 1px solid #f85959;
  }

  /deep/ .grid-item {
    width: 160px;
    height: 86px;
    .van-grid-item__content {
      white-space: nowrap;
      background-color: #f4f5f6;
      .van-grid-item__text, .text {
        font-size: 28px;
        color: #222;
        margin-top: 0;
      }
      .active {
        color: red;
      }
      .van-grid-item__icon-wrapper {
        position: unset;
      }
    }
  }

  /deep/ .my-grid {
    .grid-item {
      .van-icon-clear {
        position: absolute;
        right: -10px;
        top: -10px;
        font-size: 30px;
        color: #cacaca;
        z-index: 2;
      }
    }
  }

  /deep/ .recommend-grid {
    .grid-item {
      .van-grid-item__content {
        flex-direction: row;
        .van-icon-plus {
          font-size: 28px;
          margin-right: 6px;
        }
      }
    }
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

# 4、展示我的频道

思路: 我的频道数据,其实就是首页那个频道数据,我们现在只需要通过父子通信传递到当前频道编辑组件即可

1、在父组件中把 channels 传递给频道编辑组件

image-20200316002816033

2、在子组件频道编辑中声明userChannels接收父组件的传递过来的数据,并渲染

image-20210701224610027 image-20210701224752842

# 5、让激活频道高亮

思路:

  • 将首页中的激活的标签索引传递给频道编辑组件
  • 在频道编辑组件中遍历我的频道列表的时候判断遍历项的索引是否等于激活的频道标签索引,如果一样则作用一个高亮的 CSS 类名

1、将首页组件中的 active 传递到频道编辑组件中

image-20210701225012437

2、在频道编辑组件中声明 props 接收

image-20210701225158141

3、判断遍历项,如果 遍历项索引 === active,则给这个频道项设置高亮样式

image-20210701225337010 image-20200316004847629

# 6、展示推荐频道列表

1571040968593

没有用来获取推荐频道的数据接口,但是我们有获取所有频道列表的数据接口。

所以:所有频道列表 - 我的频道 = 剩余推荐的频道

实现过程所以一共分为两大步:

  • 获取所有频道
  • 基于所有频道和我的频道计算获取剩余的推荐频道

# 6.1、获取所有频道

1、创建src/api/channel.js,封装数据接口

import  request from  '@/utils/request'

/**
 * 获取所有频道
 */
export const getAllChannels = () => {
  return request({
    method: 'GET',
    url: '/v1_0/channels'
  })
}
1
2
3
4
5
6
7
8
9
10
11

2、在编辑频道组件中请求获取所有频道数据

image-20210701150422274 image-20200316021948407 image-20200316022017473

3、在调试工具中测试是否有拿到数据

# 6.2、处理展示推荐频道

思路:所有频道 - 用户频道 = 推荐频道

1、封装计算属性筛选数据

computed:{
    recommentChannels(){
        let arr = []  // 推荐数据
        // 遍历所有频道
        this.allChannels.forEach(channel=>{
            // 遍历的元素是不是在我的频道里面的内容,如果不是,就说明是推荐频道
            let ret = this.myChannels.find(myChannel=>{
                return myChannel.id === channel.id
            })
            if(!ret){
                arr.push(channel)
            }
        })
        return arr
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
image-20210701225626869
  • 思路
    • 遍历所有频道
    • 对每一个频道都判断:该频道是否属于我的频道
    • 如果不属于我的频道,则收集起来
    • 直到遍历结束,收集起来那些就是推荐频道

2、模板绑定








 
 

 




<!-- 频道推荐 -->
<van-cell :border="false">
    <div slot="title" class="title-text">频道推荐</div>
</van-cell>
<van-grid class="recommend-grid" :gutter="10">
    <van-grid-item
                   class="grid-item"
                   v-for="(channel, index) in recommendChannels"
                   :key="index"
                   icon="plus"
                   :text="channel.name"
                   />
</van-grid>
<!-- /频道推荐 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 7、添加频道

频道管理-添加频道

思路:

  • 给推荐频道列表中每一项注册点击事件
  • 获取点击的频道项
  • 将频道项添加到我的频道中
  • 将当前点击的频道项从推荐频道中移除
    • 不需要删除,因为我们获取数据使用的是计算属性,当我频道发生改变,计算属性重新求值了

1、给推荐频道中的频道注册点击事件












 




<!-- 频道推荐 -->
<van-cell :border="false">
    <div slot="title" class="title-text">频道推荐</div>
</van-cell>
<van-grid class="recommend-grid" :gutter="10">
    <van-grid-item
                   class="grid-item"
                   v-for="(channel, index) in recommendChannels"
                   :key="index"
                   icon="plus"
                   :text="channel.name"
                    @click="onAddChannel(channel)"
                   />
</van-grid>
<!-- /频道推荐 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

2、在添加频道事件处理函数中

image-20210701225851682

然后你会神奇的发现点击的那个推荐频道跑到我的频道中了,我们并没有去手动的删除点击的这个推荐频道,但是它没了!主要是因为推荐频道是通过一个计算属性获取的,计算属性中使用了 channels(我的频道)数据,所以只要我的频道中的数据发生变化,那么计算属性就会重新运算获取最新的数据。

# 8、编辑频道

思路:

  • 给我的频道中的频道项注册点击事件
  • 在事件处理函数中
    • 如果是编辑状态,则执行删除频道操作
    • 如果是非编辑状态,则执行切换频道操作

# 8.1、处理编辑状态

1、在 data 中添加数据用来控制编辑状态的显示

image-20210701151644213

3、在我的频道项中添加删除图标

image-20210701151914259

3、处理点击编辑按钮

image-20210701151748035

需求分析

image-20210701152502884

# 8.2、切换频道(跳转Tab)

功能需求:在非编辑器状态下切换频道。

1、给我的频道项注册点击事件

image-20210701220251908

2、处理函数

image-20210701220624299

3、在父组件home/index.vue中监听处理自定义事件

image-20210701220833117

image-20210701220943553

# 8.3、删除频道

功能需求:在编辑状态下删除频道。

image-20210701221431329

父组件home/index.vue里面的监听方法进行调整

image-20210701221729258

# 9、频道数据持久化

# 9.1、业务分析

频道编辑这个功能,无论用户是否登录用户都可以使用。

不登录也能使用

  • 数据存储在本地
  • 不支持同步功能

登录也能使用

  • 数据存储在线上后台服务器
  • 更换不同的设备可以同步数据

# 9.2、添加频道

思路:

  • 如果未登录,则存储到本地
  • 如果已登录,则存储到线上
    • 找到数据接口
    • 封装请求方法
    • 请求调用

1、封装添加频道的请求方法 src/api/channel.js

/**
 * 添加用户频道
 */
export const addUserChannel = channel => {
  return request({
    method: 'PATCH',
    url: '/v1_0/user/channels',
    data: {
      channels: [channel]
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12

2、导入请求方法,导入辅助函数mapState 获取user来判断是否登录了,导入本地存储封装方法



 

 
 



 



import {
  getAllChannels,
  addUserChannel  // <====增加
} from '@/api/channel'
import { mapState } from 'vuex'    // <====增加
import { setItem } from '@/utils/storage'   // <====增加


computed: {
    ...mapState(['user']),  // <====增加
    // 其他内容...    
 }
1
2
3
4
5
6
7
8
9
10
11
12

3、修改添加频道的处理逻辑



 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


async onAddChannel (channel) {
    this.myChannels.push(channel)
    // 数据持久化处理
    if (this.user) {
        try {
          // 已登录,把数据请求接口放到线上
          await addUserChannel({
            id: channel.id, // 频道ID
            seq: this.myChannels.length // 序号
          })
        } catch (err) {
          this.$toast('保存失败,请稍后重试')
        }
    } else {
        // 未登录,把数据存储到本地
        setItem('TOUTIAO_CHANNELS', this.myChannels)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 9.3、删除频道

思路:

  • 如果未登录,则存储到本地
  • 如果已登录,则存储到线上
    • 找到数据接口
    • 封装请求方法
    • 请求调用

1、封装删除用户频道请求方法

/**
 * 删除用户频道
 */
export const deleteUserChannel = channelId => {
  return request({
    method: 'DELETE',
    url: `/v1_0/user/channels/${channelId}`
  })
}
1
2
3
4
5
6
7
8
9

2、导入请求方法




 


import {
  getAllChannels,
  addUserChannel,
  deleteUserChannel // <====增加
} from '@/api/channel'
1
2
3
4
5

2、修改删除频道的处理逻辑

image-20210701230709842

# 9.4、正确获取首页频道数据

首页-获取频道列表

提示:获取登录用户的频道列表和获取默认推荐的频道列表是同一个数据接口。后端会根据接口中的 token 来判定返回数据。

image-20210701231011849 image-20210701230859515