# 频道编辑操作
- 汉堡图标
<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('添加频道失败')
}
},