移动端项目 头条首页 弹出层频道编辑

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 - 1true)
    	}
	}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
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值