频道编辑操作
需求:登录的用户,可以选择喜欢的频道
准备组件布局
目标:实现频道组件基本布局
- 封装独立的组件
组件路径:
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;
}
}
总结:
- 控制弹窗的显示和隐藏
- 父子组件的数据传递
- 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>
总结:
- 父组件向子组件传值
- 属性的类型检测
- 类的绑定用法
总结
- 更多操作
- 不刚兴趣
- 举报文章
- 父组件将文章的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('获取所有频道失败')
}
},
总结:
- 已经拥有【我的频道】和【全部频道】数据
- 基于上述两种频道计算【可选频道】数据
- 弹窗打开时触发加载全部频道的动作(基于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>
总结:
- 计算可选频道(算法)
- 动态渲染可选频道数据
点击进入频道
目标:控制点击进入频道
- 事件绑定
<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>
总结
- 如果希望传递给子组件的属性是双向绑定的,可以再属性的后面添加.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)
},
总结:
- 封装接口
- 绑定事件调用接口
- 删除频道(子向父组件传值,由父组件根据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使用
总结:
- 频道需要排序(每个频道添加seq属性)
- 提交的数据不可以包含【推荐】频道
添加我的频道
目标:封装添加频道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('添加我的频道失败')
}
},