目录
首页-频道编辑
一、处理页面弹出层
在home/index.vue组件页面中使用van-popup组件制作弹出层页面
<van-popup
v-model="isChannelEditShow"
position="bottom"
class="channel-edit-popup"
closeable
close-icon-position="top-right"
get-container="body"
style="height: 100%;"
>内容</van-popup
>
给弹出层初始化isChannelEditShow: false到data中,用于控制弹出层显隐
在当前页面中van-tabs标签添加一个插槽,控制弹出层的显隐
<div slot="nav-right" @click="isChannelEditShow = true" class="wap-nav-wrap">
<van-icon name="wap-nav"></van-icon>
</div>
调整插槽按钮的样式
.wap-nav-wrap {
position: fixed;
right: 0;
width: 33px;
/* height: 43px; */
height: 1rem;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
opacity: 0.9;
.van-icon {
font-size: 0.6rem;
}
/* 添加竖线 */
&:before {
content: "";
height: 29px;
width: 1px;
/* background: url("./line.png") no-repeat; */
background-size: contain;
position: absolute;
left: 0;
top: 0;
}
}
此时会出现一个问题,标签滑动到最后一个时插槽按钮会遮挡菜单文字部分,如下 :
解决办法是在插槽按钮的前面添加一个占位元素,并给一定的宽度,此时宽度并未显示,经过审查元素后发现每一个van-tab标签都有一个flex属性,平分空间,所以给插槽按钮再添加一个flex-shrink:0属性
<!-- 插槽按钮定位把列表最后的位置给挡住了,解决办法是在这里添加一个占位元素 -->
<div slot="nav-right" class="wap-nav-placeholder"></div>
<!-- 添加一个插槽,控制弹出层的显隐 -->
<div
slot="nav-right"
@click="isChannelEditShow = true"
class="wap-nav-wrap"
>
<van-icon name="wap-nav"></van-icon>
</div>
调整样式:
.wap-nav-placeholder {
width: 33px;
flex-shrink: 0;
}
二、创建频道编辑组件
home/components/channel-edit.vue中封装频道编辑组件
<!-- 频道编辑组件 -->
<template>
<div class="channel-edit">14122</div>
</template>
<script>
export default {
name: "channelEdit",
components: {},
props: {},
data() {
return {};
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {}
};
</script>
<style scoped lang="less"></style>
home/index.vue使用频道编辑的组件
- 引入组件模块 import ChannelEdit from "./components/channel-edit";
- 注册组件
- 在弹出层中使用组件 <Channel-edit></Channel-edit>
三、页面布局
<div class="channel-edit">
<!-- 我的频道 -->
<van-cell center :border="false">
<div slot="title">我的频道</div>
<van-button type="danger" size="mini" plain round>编辑</van-button>
</van-cell>
<!-- 宫格组件 -->
<van-grid :gutter="10">
<van-grid-item text="value" v-for="value in 8" />
</van-grid>
<!-- 频道推荐 -->
<van-cell center :border="false">
<div slot="title">频道推荐</div>
<van-button type="danger" size="mini" plain round>编辑</van-button>
</van-cell>
<van-grid :gutter="10">
<van-grid-item text="value" v-for="value in 8" />
</van-grid>
</div>
四、展示我的频道
home/index.vue父组件传频道列表的数据给channel-edit.vue子组件,子组件用props接收数据
父组件
<Channel-edit :user-channels="channels"></Channel-edit>
子组件
props: {
userChannels: {
type: Array,
required: true
}
}
子组件用获取到的数据在页面上渲染数据
<van-grid :gutter="10">
<van-grid-item
class="grid-item"
text="value"
v-for="(channel, index) in userChannels"
:key="index"
:text="channel.name"
/>
</van-grid>
五、展示推荐频道列表
没有用来获取推荐频道的数据接口,只有获取所有的频道列表的数据接口,所以:所有频道列表-我的频道=剩余推荐的频道,实现过程分两步:
- 获取所有频道
- 基于所有频道和我的频道计算获取剩余的推荐频道
5.1 获取所有频道
创建api/channel.js文件,封装数据接口
// 频道相关请求模块
import request from '@/utils/request'
// 获取所有频道列表
export const getAllChannels = () => {
return request({
method: 'GET',
url: '/app/v1_0/channels'
})
}
在编辑频道组件中请求获取所有频道数据
在调试工具中测试是否有拿到数据
5.2 处理展示推荐频道
获取推荐频道数据:所有频道列表-我的频道=剩余推荐的频道
- 封装计算属性筛选数据
- 遍历所有频道
- 对每一个频道都判断:该频道是否属于我的频道
- 如果不属于我的频道则收集起来
- 直到遍历结束,剩下来就是那些简便的推荐频道
computed: {
// 推荐的频道列表
recommendChannels() {
// 法一:
// 所有频道 - 我的频道 = 剩下的推荐频道
// filter方法:过滤数据,根据方法返回的布尔值true 来收集数据
// filter 查找满足条件的所有元素
// filter返回一个新数组,
// return this.allChannels.filter(channel => {
// // 判断channel是否属于“我的频道”;! 取反
// // find 查找满足条件的单个元素
// return !this.userChannels.find(userChannel => {
// // 找到满足该条件的元素
// return userChannel.id === channel.id;
// });
// });
// 法二:
const arr = [];
// 遍历所有频道
this.allChannels.forEach(channel => {
let flag = false; // 默认不属于
for (let i = 0; i < this.userChannels.length; i++) {
if (this.userChannels[i].id === channel.id) {
// 所有频道中的频道项属于我的频道
flag = true;
break;
}
}
if (!flag) {
arr.push(channel);
}
});
return arr;
}
},
模板绑定,展示推荐频道
<van-grid :gutter="10">
<van-grid-item
class="grid-item"
text="value"
v-for="(channel, index) in recommendChannels"
:key="index"
:text="channel.name"
/>
</van-grid>
六、添加频道
给推荐频道列表中每一项注册点击事件@click="onAdd(channel)"
处理onAdd事件函数,获取点击的频道项,将频道项添加到我的频道中
// 添加频道
onAdd(channel) {
this.userChannels.push(channel);
// TODO:数据持久化,这一步未做,后续讲解
}
然后会发现点击的推荐频道会跑到我的频道中,但并没有手动删除点击的推荐频道,但是在推荐频道中没有了(不需要删除,因为获取数据使用的是计算属性,当我的频道发生改变时,计算属性会重新求值),这是因为计算属性使用了channels(我的频道)数据,所以只要我的频道数据发生变化,那计算属性也会重新运算获取最新的数据
七、编辑频道
7.1 处理编辑的显示状态
给宫格组件的van-grid-item添加icon属性,设置显示的icon图标,通过调整样式固定图标的位置;
并给icon动态绑定isEdit,用来控制编辑的显示状态,数据初始化为isEdit: false,若isEdit为true,则显示图标,否则隐藏。
<van-grid :gutter="10">
<van-grid-item
class="grid-item"
:icon="isEdit ? 'clear' : ''"
v-for="(channel, index) in userChannels"
:key="index"
:text="channel.name"
/>
</van-grid>
为“编辑”按钮添加点击事件,来回切换控制图标的显隐,同时设置其文字,若图标显示,则文字为完成状态,若图标不显示,则显示编辑状态
<van-button
type="danger"
size="mini"
plain
round
@click="isEdit = !isEdit"
>{{ isEdit ? "完成" : "编辑" }}
</van-button>
效果:编辑状态/未编辑状态
7.2 切换频道
需求:编辑状态,则需要删除频道;非编辑状态,则需要切换频道;
给频道项绑定点击事件 @click="onUserChannelClick(index)"
处理onUserChannelClick事件函数,如果isEdit为true,则进行删除操作,如果为false,则进行切换操作
推荐频道一般是不允许删除的,优化前一部分动态绑定icon的代码: :icon="isEdit && index !== 0 ? 'clear' : ''"
onUserChannelClick(index) {
// 推荐频道是固定的,不允许删除
if (this.isEdit && index !== 0) {
// 编辑状态,则需要删除频道
this.deleteChannel(index);
} else {
//非编辑状态,则需要切换频道;
this.switchChannel(index);
}
},
// 删除频道
deleteChannel(index) {
this.userChannels.splice(index, 1);
// TODO:数据持久化,待讲解
},
// 切换频道
switchChannel(index) {}
7.3 让激活频道高亮
1.在子组件中定义一人自定义事件 this.$emit("update-active", index);
2.在父组件中接收事件,@update-active="onUpdateActive",并处理onUpdateActive事件函数
onUpdateActive(index) {
this.active = index;
}
或者将第2步修改为:@update-active="active = $event"
模块中的$event表示事件参数,即this.$emit("update-active", index);的index传的值为1,则$event为1;若传的值为2,则$event为2
3.让被点击的“我的频道”中频道项激活::class="{ active: index === active }" 第一个active是类名
7.4 删除频道
解决删除频道激活索引的问题:用户删除当前频道的后一个频道,则高亮显示是正常的;若删除当前频道的前一个频道,则高亮的值active仍然不变,但高亮的内容对应不上,因此在删除前一个频道时候需要重新更新激活频道的索引
八、频道数据持久化
8.1 业务分析
频道编辑这个功能,无论用户是否登录用户都可以使用
不登录也能使用
- 数据存储在本地
- 不支持同步功能
登录也能使用
- 数据存储在线上后台服务器
- 更换不同的设备可以同步数据
8.2 添加频道
1.登录状态存储
配置对收到的用户频道 新增的保存的网络请求:api/channel.js
// 本接口对收到的用户频道 新增的保存,原有的覆盖顺序序号,但不会删除未涉及到的用户频道
export const addUserChannel = data => {
return request({
method: 'PATCH',
url: '/app/v1_0/user/channels',
data
})
}
channel-edit.vue中加载该接口的模块
import {addUserChannel} from "@/api/channel";
控制台中查看网络请求详情,点击“频道推荐”新增一条“我的频道”发起请求
代码:
// 添加频道
async onAdd(channel) {
this.userChannels.push(channel);
// TODO:数据持久化,待讲解
if (this.user) {
// 登录了,数据存储到线上
await addUserChannel({
channels: [
{
id: channel.id,
seq: this.userChannels.length
}
]
});
} else {
// 没有登录,数据存储到本地
setItem("USER_CHANNELS", this.userChannels);
}
},
8.3 删除频道
用户未登录,删除频道时数据存储到本地上 setItem("user-channels", this.userChannels);
控制台测试数据样例:用户点击频道推荐进行添加操作或点击我的频道进行删除操作,本地的channel数组会对应的添加或删除
用户登录,在api/channel.js中发起网络请求:
// 删除用户指定频道
export const deleteUserChannel = channelId => {
return request({
method: 'DELETE',
url: `/app/v1_0/user/channels/${channelId}`
})
}
channel-edit.vue中加载该接口的模块
import {deleteUserChannel} from "@/api/channel";
测试:用户点击我的频道-》编辑,进行删除操作,networkj里发起删除的网络请求,刷新页面,数据确实被删了
代码:
// 删除频道
async deleteChannel(channel, index) {
// 如果删除的是当前激活频道之前的频道,
if (index <= this.active) {
// 更新激活频道的索引
this.$emit("update-active", this.active - 1);
}
this.userChannels.splice(index, 1);
// TODO:数据持久化,待讲解
if (this.user) {
await deleteUserChannel(channel.id);
} else {
setItem("user-channels", this.userChannels);
}
},
九、正确的获取用户首页频道列表数据
具体的流程如下
提示:获取登录用户的频道列表和获取默认推荐的频道列表是同一个数据接口,后端会根据接口中的token来判定返回数据
home/index.vue获取登录状态
import { mapState } from "vuex";
computed: {
...mapState(["user"])
},
修改home/index.vue里的loadChannels事件函数,加载模块 import { getItem } from "@/utils/storage";
async loadChannels() {
// const { data } = await getUserChannels();
// this.channels = data.data.channels;
let channels = [];
if (this.user) {
// 用户已登陆,请求获取线上的用户频道列表
const { data } = await getUserChannels();
channels = data.data.channels;
} else {
// 用户未登陆,则判断是否有本地存储的频道列表数据
const localChannels = getItem("USER_CHANNELS");
// 如果有本地存储的频道列表则使用
if (localChannels) {
// 本地存储有数据
channels = localChannels;
} else {
// 用户没有登录,也没有本地存储的频道,就请求获取默认推荐的频道列表
// 本地存储无数据
const { data } = await getUserChannels();
channels = data.data.channels;
}
}
// 把处理好的数据放到data中以供模板使用
this.channels = channels;
},
该接口不强制用户登录,匿名用户返回后台设置的默认频道列表
测试:
用户没有登录但本地存储有数据,则显示本地存储里的数据
用户没有登录也没有本地存储数据,则默认请求获取默认推荐的频道列表