# 频道编辑操作

  • 汉堡图标
<span @click='showEditChannel=true' class="bar_btn" slot="nav-right">
  <van-icon name="wap-nav"></van-icon>
</span>

# 准备组件布局

目标:实现频道组件基本布局

  • 封装独立的组件

组件路径:src/components/EditChannel.vue

<!-- @closed="editing=false" 关闭屉式菜单  重置编辑状态为不编辑 -->
<van-action-sheet :value="value" @closed="editing=false" @input="$emit('input', $event)" title="编辑频道">
  <div class="channel">
    <div class="tit">
      我的频道:
      <span class="tip">点击可进入频道</span>
      <van-button v-if="!editing" @click="editing=true" size="mini" type="info" plain>编辑</van-button>
      <van-button v-else @click="editing=false" size="mini" type="danger"  plain>完成</van-button>
    </div>
    <van-grid class="van-hairline--left">
      <van-grid-item v-for="index in 8" :key="index">
        <span class="f12">频道{{index}}</span>
        <van-icon v-show="editing" class="btn" name="cross"></van-icon>
      </van-grid-item>
    </van-grid>
  </div>
  <div class="channel">
    <div class="tit">可选频道:</div>
    <van-grid class="van-hairline--left">
      <van-grid-item v-for="index in 8" :key="index">
        <span class="f12">频道{{index}}</span>
        <van-icon class="btn" name="plus"></van-icon>
      </van-grid-item>
    </van-grid>
  </div>
</van-action-sheet>
props: {
    value: {
      type: Boolean,
      default: false
    }
}
data () {
    return {
      editing: false
    }
}
  • 控制组件的显示
<span @click="isEdit=true" class="bar_btn">
  <van-icon name="wap-nav"></van-icon>
</span>
// data中添加数据控制组件弹窗
isEdit: false
<channel-edit v-model="isEdit"></channel-edit>
  • 样式处理
.van-popup--bottom{
  &.van-popup--round{
    border-radius: 0;
  }
}
.van-action-sheet {
  max-height: 100%;
  height: 100%;
  .van-action-sheet__header {
    background: #3296fa;
    color: #fff;
    .van-icon-close {
      color: #fff;
    }
  }
}
.channel {
  padding: 10px;
  .tit{
    line-height: 3;
    .tip {
      font-size: 10px;
      color: #999;
    }
  }
  .van-button {
    float: right;
    margin-top: 7px;
  }
  .btn{
    position: absolute;
    bottom: 0;
    right: 0;
    background: #ddd;
    font-size: 12px;
    color: #fff;
  }
  .f12{
      font-size:12px;
      color: #555;
  }
  .red{
    color: red;
  }
}

# 渲染我的频道

目标:渲染频道列表数据

  • 父组件传递数据给子组件
<!-- 编辑频道组件 -->
<channel-edit
  v-model='showEditChannel'
  :channels='channels'
  :active='active'
></channel-edit>
  • 子组件接收父组件数据
  props: {
    value: {
      type: Boolean,
      default: false
    },
    channels: {
      type: Array,
      default: () => []
    },
    active: {
      type: Number,
      default: 0
    }
  },
  data () {
    return {
      // 默认是未编辑状态
      editing: false,
      // 全部频道
      channels: []
    }
  },
  • 数据动态渲染
<div class="channel">
  <div class="tit">
    我的频道:
    <span class="tip">点击可进入频道</span>
    <van-button v-if="!editing" @click="editing=true" size="mini" type="info" plain>编辑
    </van-button>
    <van-button v-else size="mini" type="danger" @click="editing=false" plain>
      完成
    </van-button>
  </div>
  <van-grid class="van-hairline--left">
    <van-grid-item v-for="(item, index) in channels" :key="item.id">
      <span class="f12" :class="{ red: active===index }">
        {{item.name}}
      </span>
      <van-icon  v-if="editing && i!==0" class="btn" name="cross">
      </van-icon>
    </van-grid-item>
  </van-grid>
</div>

# 渲染可选频道

目标:渲染可选频道

  • 封装接口调用方法
import request from '@/utils/request.js'
export const getAllChannels = () => {
  return request({
    method: 'get',
    url: 'app/v1_0/channels'
  })
}
  • 组件中调用方法获取全部频道数据
methods: {
  async getAllChannels () {
    try {
      const ret = await getAllChannels()
      this.allChannels = ret.data.channels
    } catch (e) {
      console.log(e)
      this.$toast.fail('获取所有频道失败')
    }
  }
}
created () {
    this.getAllChannels()
},
  • 根据 【全部频道】 和 【我的频道】 得到 【可选频道】
    • 可选频道 = 全部频道 - 我的频道
computed: {
  // 计算可选频道
  selectChannels () {
    return this.allChannels.filter(item => {
      // item是所有频道其中之一
      // item不存在于我的频道
      // some的作用:判断数组中是否有满足条件的数据,只要有一个就返回true
      return !this.channels.some(channel => {
        // channel是我的频道其中之一
        return channel.id === item.id
      })
    })
  }
},
  • 动态渲染
<div class="channel">
  <div class="tit">可选频道:</div>
  <van-grid class="van-hairline--left">
    <van-grid-item v-for="channel in selectChannels" :key="channel.id">
      <span class="f12">{{channel.name}}</span>
      <van-icon name="plus" class="btn"></van-icon>
    </van-grid-item>
  </van-grid>
</div>

# 点击进入频道

目标:控制点击进入频道

  • 事件绑定
<span class="f12" @click="enterChannel(index)" 
  • 事件函数定义
enterChannel (index) {
  // 进入频道列表
  // 1、关闭窗口
  this.$emit('input', false)
  // 2、修改父组件中频道的索引activeIndex
  this.$emit('update-aindex', index)
}
  • 父组件监听事件
<edit-channel
  v-model="showEditChannel"
  :channels="channels"
  @update-index="active=$event"
  :activeIndex="activeIndex">
</edit-channel>
  • 简写方法
    • 父子组件之间传值的简化写法
      • 父向子传值 :属性名称 (:active="active")
      • 子向父传值 @自定义事件名称 (@update-index="active=$event")
      • 合并的写法 :active.sync="active"
    enterChannel (index) {
      this.$emit('input', false)
-     this.$emit('update-index', index)
+     this.$emit('update:active', index)
    },
    <edit-channel
      v-model="showEditChannel"
      :channels="channels"
-      @update-index="active=$event"
-      :active="active">
+      :active.sync="activeIndex">
    </edit-channel>

# 回顾

  • 列表的下拉刷新和加载更多
  • 日期的格式化
    • Vue插件的用法
    • dayjs包的用法
  • 图片懒(延迟)加载
  • 更多操作
    • 弹窗组件的控制
    • 获取文章的id
    • 不感兴趣
    • 举报文章(异常处理)
  • 频道编辑
    • 弹出层的控制
    • 实现我的频道和可选频道的基本布局
    • 动态渲染我的频道
    • 调用接口获取所有频道数据
    • 动态计算可选频道的数据(两个数组的差集)
    • 动态渲染可选频道
    • 点击我的频道进入列表页
    • 关于sync修饰符的用法

# 删除我的频道

目标:实现删除我的频道功能

  • 封装上传频道接口
// 调用接口删除频道
export const delChannel = (channelId) => {
  return request({
    method: 'delete',
    url: 'app/v1_0/user/channels/' + channelId
  })
}
  • 绑定删除频道事件
<van-icon @click="deleteChannel(channel.id, i)"></van-icon>
  • 调用接口删除频道
async deleteChannel (id, index) {
  // 删除频道
  try {
    // 调用接口删除频道
    await delChannel(id)
    // 页面中删除该频道
    this.$emit('delete-success', index)
  } catch (e) {
    console.log(e)
    this.$toast.fail('删除频道失败')
  }
},
<edit-channel
  @delete-success='deleteSuccess'
  :active.sync='active'
  :channels='channels'
  v-model='isEdit'>
</edit-channel>
deleteSuccess (index) {
  // 根据子组件传递的索引,删除频道
  this.channels.splice(index, 1)
},

# 添加我的频道接口参数设置

目标:添加我的频道参数处理

  • 绑定事件,提供(添加频道)数据。
<van-icon name="plus" @click="addChannel(item)" class="btn"></van-icon>
  • 接口需要实现 调用后台接口与本地存储功能
    • 后台需要排序 [{id:'频道ID',seq,'排序'}]
    • 本地需要 {id:'频道ID',name:'频道名称'}
// 因为无法获取后台seq顺号  只能前端排序让后端统一即可。
// 后端需要 完整的排好序的 数组 [{id,seq},...] 注意:不需要推荐
// 本地需要 {id, name}  综合一下:格式如下
const orderChannels = this.channels.map((item, i) => ({
  id: item.id,
  name: item.name,
  seq: i
}))
// 添加频道
orderChannels.push({ id, name, seq: orderChannels.length })
// 删除推荐(后端接口要求,不能提交推荐频道)
orderChannels.splice(0, 1)

orderChannels 提供给API使用

# 添加我的频道

目标:封装添加频道API

  • 封装添加频道接口方法
// 添加频道
// 添加频道的接口
export const addChannel = (orderChannels) => {
  return request({
    method: 'put',
    url: 'app/v1_0/user/channels',
    data: {
      channels: orderChannels
    }
  })
}
  • 组件实现添加频道功能
    • 绑定添加频道的按钮的点击事件
    • 封装了添加频道的接口方法
    • 准备添加频道调用接口的相关参数
      • 要求是数组,数组中放对象,对象中包含频道id和排序序号seq
      • seq作用:告诉后端,页面中频道的顺序
      • 数组中要去掉【推荐】频道
    • 调用接口发送请求
    • 如果调用接口成功,在我的频道中添加一个新的频道(点击的添加的频道)
import { addChannel } from '@/api/channel.js'
async addChannel (channel) {
  // 添加频道,要求参数是如下格式
  // seq用于实现当前我的频道的排序
  const orderChannels = this.channels.map((item, index) => ({
    id: item.id,
    name: item.name,
    seq: index
  }))
  orderChannels.push({
    id: channel.id,
    name: channel.name,
    seq: orderChannels.length
  })
  // 后端不让添加【推荐】频道
  if (orderChannels[0].id === 0) {
    orderChannels.splice(0, 1)
  }
  try {
    // 调用接口添加频道
    await addChannel(orderChannels)
    // 通知父组件添加该频道
    this.$emit('add-success', {
      id: channel.id,
      name: channel.name,
      articles: [],
      loading: false,
      finished: false,
      isLoading: false,
      timestamp: Date.now(),
      error: false
    })
    // this.channels.push({
    //   id: channel.id,
    //   name: channel.name,
    //   articles: [],
    //   loading: false,
    //   finished: false,
    //   isLoading: false,
    //   timestamp: Date.now(),
    //   error: false
    // })
  } catch (e) {
    console.log(e)
    this.$toast.fail('添加频道失败')
  }
},