ui效果图:
一、添加popup弹出层组件,高100%占满屏幕,留一个左上侧×号
home/index.vue
<van-popup
v-model="isPopupShow"
closeable
close-icon-position="top-left"
position="bottom"
:style="{ height: '100%' }"
/>
当isPopupShow=true 弹出层弹出
二、将‘我的频道‘和’频道推荐‘做成一个组件channel-edit
1.将组件引入home/index.vue中
import ChannelEdit from './components/channel-edit'
同时添加到components
components:{
ChannelEdit
},
在template中使用子组件
<channel-edit></channel-edit>
2.来到home/components/channel-edit.vue
’我的频道‘ 和’编辑‘按钮使用cell单元格, ’选择框‘使用grid
<!--我的频道-->
<van-cell :border="false">
<div slot="title">我的频道</div>
<van-button size="mini" round type="danger" plain>编辑</van-button>
</van-cell>
<!-- 选择框 -->
<van-grid :gutter="10">
<van-grid-item v-for="value in 8" :key="value" text="文字" />
</van-grid>
<!--频道推荐-->
<van-cell :border="false">
<div slot="title">频道推荐</div>
</van-cell>
<!-- 选择框 -->
<van-grid :gutter="10">
<van-grid-item v-for="value in 8" :key="value" text="文字" />
</van-grid>
3.对频道编辑页面样式进行调整
<template>
<div class="channel-edit">
<van-cell :border="false">
<div slot="title" class="title-text">我的频道</div>
<van-button class="edit-btn" size="mini" round type="danger" plain>编辑</van-button>
</van-cell>
<van-grid class="my-grid" :gutter="10">
<van-grid-item icon="clear" class="grid-item" v-for="value in 8" :key="value" text="文字" />
</van-grid>
<van-cell :border="false">
<div slot="title" class="title-text">频道推荐</div>
</van-cell>
<van-grid class="recomment-grid" :gutter="10">
<van-grid-item icon="plus" class="grid-item" v-for="value in 8" :key="value" text="文字文字字" />
</van-grid>
</div>
</template>
<style scoped lang="less">
.channel-edit {
padding: 85px 0;
.title-text {
font-size: 32px;
color: #333;
}
.edit-btn {
width: 104px;
height: 48px;
font-size: 26px;
color: #f85959;
border: 1px solid #f85959;
}
/deep/ .grid-item {
width: 160px;
height: 86px;
.van-grid-item__content {
white-space: nowrap;
background-color: #f4f5f6;
.van-grid-item__text {
font-size: 22px;
color: #222;
margin-top: 4px !important;
}
}
}
/deep/ .my-grid {
.grid-item {
.van-icon-clear {
position: absolute;
right: -10px;
top: -10px;
font-size: 30px;
color: #cacaca;
z-index: 2;
}
}
}
/deep/ .recomment-grid {
.grid-item {
width: 160px;
height: 86px;
.van-grid-item__content {
flex-direction: row;
.van-icon-plus {
font-size: 22px;
line-height: 22px;
margin-right: 6px;
}
}
}
}
}
</style>
4.展示我的频道
思路:
①我的频道内容由父组件传值而来,在父组件中把channels传递给channel-edit子组件;
<channel-edit :user-channels="channels" />
②在子组件中声明接收父组件的userChannels数据,并遍历展示
props:{
userChannels:{
type:Array,
required:true //必传
}
}
<!--我的频道 icon="clear"是右上角的小叉叉-->
<van-grid class="my-grid" :gutter="10">
<van-grid-item
icon="clear"
class="grid-item"
v-for="(item, index) in userChannels"
:key="index"
:text="item.name"
/>
</van-grid>
三、让当前激活的频道高亮显示
首页哪一个高亮 弹窗中我的频道也应该是哪一个高亮,因此要将首页的active传递给子组件中
①父组件index.vue:
<channel-edit :user-channels="channels" :active-index="active" />
②子组件在接收activeIndex
activeIndex:{
type:Number,
required:true
}
③通过插槽,渲染我的频道内容
在这里 把之前在van-grid-item中的:text="item.name"拆出来 写成{{item.name}}形式,
<van-grid class="my-grid" :gutter="10">
<van-grid-item
icon="clear"
class="grid-item"
v-for="(item, index) in userChannels"
:key="index"
>
<!-- class="text" :class="{active: index==activeIndex}" 这里指 遍历数组时,如果有此时index和父组件传来的activeIndex相等 则 此时的样式多一个active 即 字体变红色-->
<span class="text" :class="{active: index==activeIndex}" slot="text"></span>
</van-grid-item>
</van-grid>
css:
/deep/ .grid-item {
width: 160px;
height: 86px;
.van-grid-item__content {
white-space: nowrap;
background-color: #f4f5f6;
.van-grid-item__text, .text {
font-size: 22px;
color: #222;
margin-top: 4px !important;
}
.active {
color: red;
}
}
}
四、展示推荐列表
思路:由于没有获取频道的数据接口,因此 推荐频道=所有频道-我的频道
1.获取所有频道:创建 src\api\channel.js
文件,并封装数据接口
/**
* 推荐频道列表接口数据封装
*/
import request from '@/utils/request.js'
export const getAllChannels = () =>{
return request({
method:'GET',
url:'/app/v1_0/channels'
})
}
2.在编辑频道组件中导入getAllChannels 方法
import { getAllChannels } from '@/api/channel.js'
3.在编辑频道组件中请求获取 所有频道数据
//现在data中定义一个所有频道数组
data(){
return{
allChannels:[] //所有频道
}
},
created(){
this.loadAllChannels()
},
methods:{
async loadAllChannels(){
try{
const res = await getAllChannels()
console.log(res)
//将获取到的全部频道数组赋给allChannels
this.allChannels = res.data.data.channels
}catch(error){
console.log(error)
this.$toast('获取所有频道数据失败')
}
}
}
4.处理展示推荐频道
上面又说道 推荐频道=所有频道-我的频道
①写在计算属性computed中
computed:{
recommendChannele(){
const channel = []
//遍历allChannels,item时当前循环到的对象
//data.forEach(function(item, index) {
//item 就是当日按循环到的对象
//index是循环的索引,从0开始
//})
this.allChannels.forEach(item =>{
// 使用 find 遍历数组,找到满足条件的元素项
//find函数基本格式:let obj=this.list.find(item=>item.code===val)
const ret = this.userChannels.find(userChannels =>{
return userChannels.id === item.id
})
// 如果我的频道中不包括该频道项,则收集到推荐频道中
if(!ret){
channels.push(item)
}
}
}
}
②使用数据渲染到页面中
<van-grid class="recomment-grid" :gutter="10">
<van-grid-item
icon="plus"
class="grid-item"
v-for="(item,index) in recommendChannele"
:key="item.id"
:text="item.name"
/>
</van-grid>
五、添加频道(点击推荐频道,我的频道就增加一个)
思路:
- 给推荐频道列表中的每一项都要注册点击事件
- 获取点击的频道项 即active
- 将频道项添加到我的频道中
1.在添加频道中添加点击事件
@click="onAddChannel(item)"
2.添加到函数中
onAddChannel(item){
// 推荐频道添加到我的频道中去
this.userChannels.push(item)
}
六、编辑频道(编辑状态时可删除,非编辑状态点击进入首页频道处)
1.处理编辑状态,可以切换‘编辑’和‘完成’形态
//在data中添加iconShow属性来控制元素的显示隐藏
data(){
return{
iconShow:false
}
}
//将删除按钮添加为自定义插槽
<van-grid-item
icon="clear"
class="grid-item"
v-for="(item, index) in myChannels"
:key="index"
>
<!--删除小图标-->
<van-icon slot="icon" name="clear" v-show="iconShow" ></van-icon>
<span class="text" :class="{ active: index === activeIndex }" slot="text">{{ channel.name }}</span>
</van-grid-item>
<!--点击按钮 控制上面图标-->
<van-button class="edit-btn" size="mini" round type="danger" plain @click="iconShow =!iconShow">{{iconShow?'完成':'编辑'}}</van-button>
2.控制不需要删除的频道 第一个推荐就不用删除
①在data中声明
data() {
return {
fiexdChannels: [0] // 不需要编辑、删除的频道
}
}
②在 v-icon
中进行判断,是否在不需要删除的频道上添加删除 icon
<van-icon v-show="isEdit && !fiexdChannels.includes(item.id)" slot="icon" name="clear"></van-icon>
3.点击我的频道, 激活首页频道高亮
①给我的频道添加点击事件
@click="onMyChannelClick(item, index)"
// 切换频道
onMyChannelClick (channel, index) {}
②在onMyChannelClick 事件中处理实现高亮效果
思路:
- 使用
$.emit
以及$.on
实现父子关系 - 子组件将数据传递给父组件,实现高亮效果
onMyChannelClick (item, index) {
if(iconShow){
// 编辑状态,执行删除频道
}else{
// 非编辑状态,指向切换频道
//false指的是iconShow的状态 非编辑状态 iconShow为false
this.$emit('update-active', index,false)
}
}
在父组件中接收事件
<channel-edit :my-channels="channels" :active-index="active" @update-active="onUpdateActive" />
在父组件中接收子组件传递的数据
onUpdateActive(index,flag) {
this.active = index
//关闭弹窗
this.isPopupShow = flag
}
4.删除我的频道
功能需求:在编辑状态下删除频道
onMyChannelClick (item, index) {
if(iconShow){
// 编辑状态,执行删除频道
// 如果是固定频道,则不删除
if (this.fiexdChannels.includes(channel.id)) {
return
}
// 删除频道项
this.myChannels.splice(index, 1)
// 如果要删除的激活频道之前的频道,则更新激活的频道项
// 参数1:要删除的元素的开始索引
// 参数2:要删除的个数,如果不指定,则从参数 1 开始一直删除
if (index <= this.activeIndex) {
// 让激活频道索引 - 1
this.$emit('update-active', this.activeIndex - 1,true)
}
}else{
// 非编辑状态,指向切换频道
//false指的是iconShow的状态 非编辑状态 iconShow为false
this.$emit('update-active', index,false)
}
}
七、数据持久化操作
业务分析
频道编辑这个功能,无论用户是否登录用户都可以使用。
不登录也能使用
- 数据存储在本地
- 不支持同步功能
登录也能使用
- 数据存储在线上后台服务器
- 更换不同的设备可以同步数据
添加频道
思路分析:
- 如果未登录 存储到本地
- 如果已登录 存储到数据库
- 找到数据接口
- 封装请求方法
- 请求调用
1.首先获取用户的登录状态 查看已登录还是未登录
- 导入mapState方法
- 在计算属性computed中调用mapState方法,传入user用户登录的状态
- 在添加频道的方法中根据user判断是否登录
import { mapState } from 'vuex'
computed:{
...mapState(['user'])
}
// 添加频道
onAddChannel(item) {
this.myChannels.push(item)
// 数据持久化处理
if (this.user) {
// 已登录,把数据请求接口放到服务器
} else {
// 未登录,把数据存储到本地
}
}
2.处理未登录模块,导入封装的本地存储模块
import { setItem } from '@/utils/storage.js'
onAddChannel(item){
this.myChannels.push(item)
if(this.user){
}else{
setItem('TOUTIAO_CHANNELS',this.myChannels)
}
}
3.处理登录后的逻辑,封装添加频道的请求方法
export const addUserChannel = data =>{
return request({
method:'PATCH',
url:'/v1_0/channels',
data:{
channels:[channel]
}
})
}
4.在编辑频道组件中导入并使用组件
import { addUserChannel } from '@/api/channal.js'
async onAddChannel(item){
this.myChannels.push(item)
if(this.user){
try{
await addUserChannel ({
id:item.id, // 频道 ID
seq:this.myChannels.length // 序号
})
}catch(err){
console.log(err)
this.$toast('保存失败,请稍后再试')
}
}else{
setItem('TOUTIAO_CHANNELS',this.myChannels)
}
}
删除频道
1.实现思路 和添加频道思路一样
2.封装一个删除频道功能的方法deleteChannel
,并在onMyChannelClick
方法中调用
// 删除频道
deleteChannel () {},
// 切换频道
onMyChannelClick(channel, index) {
if (this.isEdit) {
// coding……略
// 处理持久化操作
this.deleteChannel()
}
}
3.在 deleteChannel
处理未登录时候的逻辑
// 删除频道
deleteChannel () {
if (this.user) {
// 已登录,则将数据更新到线上
} else {
// 未登录,将数据更新到本地
setItem('TOUTIAO_CHANNELS', this.myChannels)
}
}
4.在 deleteChannel
处理登录时候的逻辑
- 封装删除频道的接口模块
- 在编辑频道模块导入接口模块
- 调用封装的接口模块
// 删除用户频道
export const deleteUserChannel = channelId => {
return request({
method: 'DELETE',
url: `/v1_0/user/channels/${channelId}`
})
}
import { deleteUserChannel } from '@/api/channel.js'
// 删除频道
async deleteChannel(channel) {
try {
if (this.user) {
await deleteUserChannel(channel.id)
} else {
// 未登录,将数据更新到本地
setItem('TOUTIAO_CHANNELS', this.myChannels)
}
} catch (error) {
this.$toast('操作失败,请稍后重试')
}
}
5.功能实现
import { mapState } from 'vuex'
computed: {
...mapState(['user'])
}
在 src\views\home\index.vue
中导入本地存储模块中的的 Item
方法, 用于判断用户是否登录
import { getItem } from '@/utils/storage.js'
// 获取频道列表
async loadChannels() {
// try {
// const { data } = await getUserChannels()
// this.channels = data.data.channels
// } catch (err) {
// this.$toast('获取频道数据失败')
// }
let channels = []
if (this.user) {
// 已登录,请求获取用户频道列表
const { data } = await getUserChannels()
channels = data.data.channels
} else {
// 未登录,判断是否有本地的频道列表数据
const localChannels = getItem('TOUTIAO_CHANNELS')
// 如果存在本地的频道列表数据
if (localChannels) {
channels = localChannels
} else {
// 没有,请求获取默认频道列表
const { data } = await getUserChannels()
channels = data.data.channels
}
}
this.channels = channels
}