黑马项目-头条-Mobile-频道编辑操作

频道编辑操作

需求:登录的用户,可以选择喜欢的频道

准备组件布局

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

  • 封装独立的组件

组件路径:src/components/ChannelEdit.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
    }
}
  • 控制组件的显示
<spanclass="bar_btn">
  <van-icon @click='handleEdit' 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;
  }
}

总结:

  1. 控制弹窗的显示和隐藏
  2. 父子组件的数据传递
  3. v-model在组件上的用法

渲染我的频道

目标:渲染频道列表数据

  • 父组件传递数据给子组件
<!-- 编辑频道组件 -->
<channel-edit
  v-model='showEditChannel'
  :channels='channels'
  :activeIndex='active'
></channel-edit>
  • 子组件接收父组件数据
  props: {
    value: {
      type: Boolean,
      default: false
    },
    channels: {
      type: Array,
      default: () => []
    },
    activeIndex: {
      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, i) in channels" :key="item.id">
      <span class="f12" :class="{ red: activeIndex===i }">
        {{item.name}}
      </span>
      <van-icon  v-if="editing && i!==0" class="btn" name="cross">
      </van-icon>
    </van-grid-item>
  </van-grid>
</div>

总结:

  1. 父组件向子组件传值
  2. 属性的类型检测
  3. 类的绑定用法

总结

  • 更多操作
    • 不刚兴趣
    • 举报文章
    • 父组件将文章的id传递到子组件
    • 文章id值如果太大会有问题(js客户处理的数据大小有限)
    • 基于json-bigint包实现大值数据的处理
    • axios中统一处理后端返回的数据 transformResponse
    • 常量数据需要统一维护
  • 频道编辑
    • 组件的基本布局
    • 我的频道数据的动态渲染
    • 父子组件之间的数据交互
    • v-model在组件标签上的用法
    • Vant弹窗组件的用法

反馈

  • axios的transformResponse类似于响应拦截器
    • 响应拦截器中response参数不仅包含数据,还包括响应头,配置选项等相关信息。
    • transformResponse回调参数data表示的特指服务器返回的原始数据,专门用于处理数据。
  • axios处理大数

原生js能处理的数值最大值是有限制的,如果一个超出了最大值,那么js就无法准确存储。

可以基于第三方包json-bigint处理大数(本质上就是把大数作为字符串拆分成多端进行存储)

从使用的角度:JSONBig.parse() 类似于JSON.parse() 方法的功能,但是要多出一个功能:对json中的超大数值进行自动处理,转换为一个BigNumber的实例对象(其中包含了一个数组)。该对象在使用的时候需要调用toString()方法转换数值为字符串。

渲染可选频道

目标:渲染可选频道

  • 封装接口调用方法
import request from '@/utils/request.js'
// 获取所有频道数据
export const getChannels = () => {
  return request({
    method: 'get',
    url: 'v1_0/channels'
  })
}
  • 组件中调用方法获取全部频道数据
// 弹窗打开时触发
async handleOpen () {
    // 调用接口获取所有的频道数据
    try {
        const ret = await getChannels()
        this.allChannels = ret.data.channels
    } catch {
        this.$toast('获取所有频道失败')
    }
},

总结:

  1. 已经拥有【我的频道】和【全部频道】数据
  2. 基于上述两种频道计算【可选频道】数据
  3. 弹窗打开时触发加载全部频道的动作(基于Vant组件的事件open)
  • 根据 【全部频道】 和 【我的频道】 得到 【可选频道】
    • 可选频道 = 全部频道 - 我的频道
  computed: {
    // 计算可选频道的数据
    optionChannels () {
      // 已知所有频道数据
      // 当前我的频道数据
      return this.allChannels.filter((item) => {
        // 过滤的条件:item不在我的频道数据中
        // arr.some方法作用:判断数组中是否包含符合条件的数据,只要有一项符合,就返回true
        return !this.channels.some((channel) => {
          // 让item和我的频道中每一项数据相比
          return channel.id === item.id
        })
      })
    }
    // optionChannels () {
    //   // 已知所有频道数据
    //   // 当前我的频道数据
    //   const arr = this.allChannels.filter((item) => {
    //     // 过滤的条件:item不在我的频道数据中
    //     // arr.some方法作用:判断数组中是否包含符合条件的数据,只要有一项符合,就返回true
    //     const ret = this.channels.some((channel) => {
    //       // 让item和我的频道中每一项数据相比
    //       return channel.id === item.id
    //     })
    //     return !ret
    //   })
    //   return arr
    // }
  },
  • 动态渲染
<div class="channel">
  <div class="tit">可选频道:</div>
  <van-grid class="van-hairline--left">
    <van-grid-item v-for="channel in optionalChannels" :key="channel.id">
      <span class="f12">{{channel.name}}</span>
      <van-icon name="plus" class="btn"></van-icon>
    </van-grid-item>
  </van-grid>
</div>

总结:

  1. 计算可选频道(算法)
  2. 动态渲染可选频道数据

点击进入频道

目标:控制点击进入频道

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

总结

  1. 如果希望传递给子组件的属性是双向绑定的,可以再属性的后面添加.sync即可,但是有一个要求:子组件触发的事件必须是如下的格式 update:属性名称
this.$emit('update:activeIndex', index)

删除我的频道

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

  • 封装上传频道接口
// 调用接口删除频道
export const delChannel = async (channelId) => {
  return request({
    method: 'delete',
    url: 'v1_0/user/channels/' + channelId
  })
}
  • 绑定删除频道事件
<van-icon @click="delChannel(channel.id)"></van-icon>
  • 调用接口删除频道
// 删除频道
async handleDelete (id) {
    try {
        await delChannel(id)
        // 通知父组件删除该频道
        this.$emit('del-channel', id)
    } catch {
        this.$toast('删除频道失败')
    }
},
<channel-edit
  @del-delete='deleteChannel'
  v-model='showEditChannel'
  :channels='channels'
  :activeIndex.sync='activeIndex'
  :abc.sync='abc'
></channel-edit>
// 删除频道
delChannel (id) {
    // 根据id删除对应的频道
    const index = this.channels.findIndex(item => {
        return item.id === id
    })
    this.channels.splice(index, 1)
},

总结:

  1. 封装接口
  2. 绑定事件调用接口
  3. 删除频道(子向父组件传值,由父组件根据id删除频道)

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

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

添加频道时,需要告诉后端,当前我的频道的顺序,后端不需要【推荐】频道

  • 绑定事件,提供(添加频道)数据。
<van-icon name="plus" @click="addChannel(item)" class="btn"></van-icon>
  • 接口需要实现 调用后台接口与本地存储功能
    • 后台需要排序 [{id:‘频道ID’,seq,‘排序’}]
    • 本地需要 {id:‘频道ID’,name:‘频道名称’}
// 因为无法获取后台seq顺号  只能前端排序让后端统一即可。
// 后端需要 完整的排好序的 数组 [{id,seq},...] 注意:不需要推荐
// 本地需要 {id, name}  综合一下:格式如下
// 添加频道
addChannel (channel) {
    // 准备参数:对每一个频道进行排序(添加一个seq属性进行编号);去掉【推荐】频道
    // 先对之前的频道排序
    const orderChannels = this.channels.map((item, index) => {
        return {
            id: item.id,
            name: item.name,
            seq: index
        }
    })
    // orderChannels  = [{id: '',name:'', seq: 0}, {}, {}]
    // 添加新的频道
    orderChannels.push({ id: channel.id, name: channel.name, seq: orderChannels.length })
    // 删除最开始的【推荐】频道
    orderChannels.splice(0, 1)
    console.log(orderChannels)
},

orderChannels 提供给API使用

总结:

  1. 频道需要排序(每个频道添加seq属性)
  2. 提交的数据不可以包含【推荐】频道

添加我的频道

目标:封装添加频道API

  • 封装添加频道接口方法
// 添加频道
// 添加频道的接口
export const addChannel = (orderChannels) => {
  return request({
    method: 'put',
    url: 'v1_0/user/channels',
    data: {
      channels: orderChannels
    }
  })
}
  • 组件实现添加频道功能
    • 绑定添加频道的按钮的点击事件
    • 封装了添加频道的接口方法
    • 准备添加频道调用接口的相关参数
      • 要求是数组,数组中放对象,对象中包含频道id和排序序号seq
      • seq作用:告诉后端,页面中频道的顺序
      • 数组中要去掉【推荐】频道
    • 调用接口发送请求
    • 如果调用接口成功,在我的频道中添加一个新的频道(点击的添加的频道)
import { addChannel } from '@/api/channel.js'
// 添加频道
addChannel (channel) {
    // 准备参数:对每一个频道进行排序(添加一个seq属性进行编号);去掉【推荐】频道
    // 先对之前的频道排序
    const orderChannels = this.channels.map((item, index) => {
        return {
            id: item.id,
            name: item.name,
            seq: index
        }
    })
    // orderChannels  = [{id: '',name:'', seq: 0}, {}, {}]
    // 添加新的频道
    orderChannels.push({ id: channel.id, name: channel.name, seq: orderChannels.length })
    // 删除最开始的【推荐】频道
    orderChannels.splice(0, 1)
    // 调用接口实现添加频道
    try {
        // 调用接口成功
        addChannel(orderChannels)
        // 添加频道成功后,添加页码的频道
        const newChannel = {
            // 频道的id
            id: channel.id,
            // 频道的标签名称
            name: channel.name,
            // 文章列表加载状态
            loading: false,
            // 下拉刷新的完成状态
            isLoading: false,
            // 上拉列表加载完成的标志
            finished: false,
            // 下拉刷新完成的提示信息
            pullText: '加载成功',
            // 时间戳,用于实现列表的分页查询
            timestamp: +new Date(),
            // 文章列表
            articles: []
        }
        // 把新的频道数据传递给父组件,让父组件去添加
        this.$emit('add-channel', newChannel)
    } catch {
        this.$toast('添加我的频道失败')
    }
},
上一篇:GitHub Universe 2019 Keynote 回顾


下一篇:webtest mobile + Android / Battery Historian / SoloPi