新闻头条——首页

146 阅读3分钟

一、页面搜索栏布局

1、搜索栏布局和样式

<van-nav-bar class="nav-bar">
      <van-button
        class="search-btn"
        slot="title"
        type="info"
        size="small"
        round
        icon="search">
        搜索
      </van-button>
    </van-nav-bar>
    
  .nav-bar{
    background-color: #3296fa;
    height: 129px;
    .search-btn{
      width: 445px;
      height: 65px;
      background-color: #5babfb;
      margin-top: 36px;
    }
    .van-icon {
      font-size: 32px;
    }
}

2、推荐的话题专栏模块先套用vant组件布局

<van-tabs v-model="active">
        <van-tab title="推荐">推荐</van-tab>
        <van-tab title="热门话题">热门话题</van-tab>
        <van-tab title="科技动态">科技动态</van-tab>
        <van-tab title="区块链">区块链</van-tab>
      </van-tabs>

  data () {
    return {
      active: 2
    }
  }

3、封装获取所有频道的api

  export const getChannels = () => {
      return request({
        method: 'GET',
        url: '/v1_0/channels'
      })
    }
    

调用api的数据

  data () {
    return {
      active: 2,
      channels: {}
    }
  },
  computed: {
    ...mapState(['user'])
  },
  watch: {},
  created () {
    this.loadChannels()
  },
  mounted () {},
  methods: {
    async loadChannels () {
      try {
        const { data: { data } } = await getChannels()
        this.channels = data.channels
        console.log(data)
      } catch (err) {
        this.$toast('获取频道失败')
      }
    }
  }

渲染页面

<van-tab v-for="channel in channels"
:key="channel.id" :title="channel.name">频道内容{{ channel.name }}</van-tab>

二、新闻频道栏目

1、用van-tab组件来布局新闻频道栏

<van-tabs class="channel-tab" v-model="active" swipeable animated>
    <van-tab title="标签1">内容1</van-tab>
    <van-tab title="标签2">内容2</van-tab>
    <van-tab title="标签3">内容3</van-tab>
    <van-tab title="标签4">内容4</van-tab>
    <van-tab title="标签5">内容5</van-tab>
    <van-tab title="标签6">内容6</van-tab>
    <van-tab title="标签7">内容7</van-tab>
  </van-tabs>

设置对应样式,这里注意 要加上/deep/或者>>>样式才会生效

 /deep/  .channel-tab{
    .van-tab{
      min-width: 200px;
      text-align: center;
      font-size: 27px;
      margin-bottom: 30px;
      border-right: 1px solid #edeff3;
    }
  }

2、设置汉堡按钮

使用slot插槽 nav-right属性把按钮插入新闻频道栏右侧

<van-tabs class="channel-tab" v-model="active" swipeable animated>
        <van-tab title="标签1">内容1</van-tab>
        <van-tab title="标签2">内容2</van-tab>
        <van-tab title="标签3">内容3</van-tab>
        <van-tab title="标签4">内容4</van-tab>
        <van-tab title="标签5">内容5</van-tab>
        <van-tab title="标签6">内容6</van-tab>
        <van-tab title="标签7">内容7</van-tab>
        <div slot="nav-right" class="humbarger-btn">
          <i class="iconfont icon-gengduo"></i>
        </div>
      </van-tabs>

调整样式,把汉堡按钮固定在窗口右侧,在按钮前面加一个样式使用$:before

.humbarger-btn{
      position: fixed;
      right: 0;
      opacity: 0.9;
      width: 66px;
      height: 82px;
      display: flex;
      justify-content: center;
      align-items: center;
      color: #333333;
      i .iconfont{
        font-size: 33px;
      }
固定头部之后,发现有两行文章被覆盖住了,需要在home-container加上padding-top: 174px ;

三、铺设文章列表页面

1、用List组件铺设文章列表

 <van-list
        v-model="loading"
        :finished="finished"
        finished-text="没有更多了"
        @load="onLoad"
        >
      <van-cell v-for="item in list" :key="item" :title="item" />
      </van-list>

定义方法methods

onLoad () {
      // 异步更新数据
      // setTimeout 仅做示例,真实场景中一般为 ajax 请求
      setTimeout(() => {
        for (let i = 0; i < 10; i++) {
          this.list.push(this.list.length + 1)
        }

        // 加载状态结束
        this.loading = false

        // 数据全部加载完成
        if (this.list.length >= 40) {
          this.finished = true
        }
      }, 1000)
    }

2、固定文章列表头部位置

在头部加上fixed 属性固定搜索栏
<van-nav-bar class="nav-bar" fixed>
place属性加上样式 让新闻栏也固定定位
<div slot="nav-right" class="place"></div>
.place{
      position: fixed;
      top: 92px;
      z-index: 1;
      left: 0;
      right: 0;
      width: 66px;
      height: 82px;
      flex-shrink: 0;//不参与默认空间的计算,否则宽不生效
    }

四、获取推荐频道

1、封装推荐频道的api 新建文件在api/article.js

import request from '@/utils/request'

// 新闻推荐
export const getArticles = params => {
  return request({
    method: 'GET',
    url: ' /v1_0/articles',
    params
  })
}

2、调用api的数据

async onLoad () {
      try {
        const { data } = await getArticles({
          channel_id: this.channels.id,
          timestamp: this.timestamp || Date.now()
        })
        const { results } = data.data
        this.list.push(...results)
        // ...数组的展开操作符号,会把数组元素一个个拿出来
        // 本次数据加载结束后吧状态设置结束
        this.loading = false
        // 判断是否加载完数据
        if (results.length) {
          // 更新获取数据时间戳
          this.timestamp = data.data.pre_timestamp
        } else {
          this.finished = true
        }
        console.log(data)
      } catch (err) {
        this.$toast('新闻推荐获取失败')
        this.loading = false
      }
    }

3、渲染页面

<div class="article-list">
  <van-list
    v-model="loading"
    :finished="finished"
    finished-text="没有更多了"
    @load="onLoad"
    >
  <van-cell
  v-for=" (article , index) in list"
  :key="index"
  :title="article.title" />
  </van-list>
tips:后面api接口访问数据出现这种情况,然后导致了页面渲染不成功
{
    "data": null,
    "message": "频道id或者时间戳参数缺失"
}

五、频道编辑

1、设置popup弹出层

<!-- 弹出层 -->
    <van-popup
    class="edit-channel-popup"
    v-model="isChannelEditShow"
    position="bottom"
    :style="{ height: '100%' }"
    closeable
    close-icon="close"></van-popup>
 //样式
.edit-channel-popup {
  padding-top: 100px;
  box-sizing: border-box
}
//给汉堡按钮添加点击事件
<i class="iconfont icon-gengduo" @click="isChannelEditShow=true"></i>

2、铺设频道页面

在views下的home/components创建channel-edit文件
<template>
    <div class="channel-edit">频道编辑</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下引入组件并注册
//1、引入
import ChannelEdit from '@/views/home/components/channel-edit.vue'
//2、注册
components: {
    ArticleList,
    ChannelEdit
  },
 //3、使用
<van-popup
    class="edit-channel-popup"
    v-model="isChannelEditShow"
    position="bottom"
    :style="{ height: '100%' }"
    closeable
    close-icon="close">
    <ChannelEdit></ChannelEdit>
  </van-popup>
布局频道和编辑按钮以及
<div class="channel-edit">
    <!-- 我的频道 -->
    <div class="my-channel">
        <van-cell>
            <div slot="title">我的频道</div>
            <van-button round size="mini"
                type="danger"
                style="margin-top: 10px;"
                plain>编辑</van-button>
        </van-cell>
        <van-grid :gutter="10">
        <van-grid-item class="grid-item"
            v-for="value in 8"
            :key="value"
            text="文字" />
        </van-grid>
    </div>
    <!-- 频道推荐 -->
    <div class="channel-recommand">
        <van-cell style="font-size: medium;" title="频道推荐"  />
        <van-grid :gutter="10" >
        <van-grid-item class="grid-item"
            v-for="value in 8"
            :key="value"
            text="文字" />
        </van-grid>
    </div>
</div>
设置样式
.channel-edit{
    padding:85px,0;
}
/deep/.grid-item{
    width: 160px;
    height: 86px;
    .van-grid-item__content{
        background-color: #f4f5f6;
    }
}
最终版的样式 给按钮添加删除符号和添加符号
<div class="channel-edit">
        <!-- 我的频道 -->
        <div class="my-channel">
            <van-cell>
                <div slot="title">我的频道</div>
                <van-button round size="mini"
                    type="danger"
                    style="margin-top: 10px;"
                    plain>编辑</van-button>
            </van-cell>
            <van-grid :gutter="10">
            <van-grid-item class="grid-item"
                v-for="value in 8"
                :key="value"
                icon="clear"
                text="文字" />
            </van-grid>
        </div>
        <!-- 频道推荐 -->
        <div class="channel-recommand">
            <van-cell style="font-size: medium;" title="频道推荐"  />
            <van-grid :gutter="10" >
            <van-grid-item class="grid-item"
                v-for="value in 8"
                :key="value"
                icon="plus"
                text="文字" />
            </van-grid>
        </div>
    </div>

 .channel-edit{
    padding:85px,0;
    /deep/.grid-item{
        width: 160px;
        height: 86px;
        .grid-item text{
            color: #222222;
        }
        .van-icon-clear{
            position: absolute;
            top: -10px;
            right: -10px;
            font-size: 30px;
            color: #bababa;
            }
        .van-grid-item__content{
            background-color: #f4f5f6;
        }
    }
}

/deep/.channel-recommand{
    .grid-item{
        .van-grid-item__content{
            flex-direction:row ;
            white-space: nowrap;//防止折行
            .van-icon-plus{
                font-size: 28px;
                margin-top: 15px;
                margin-right: 10px;
            }
            .van-grid-item__text{
                margin-top: 0;
            }
        }
    }
}

3、展示我的频道

我的频道的数据跟首页的推荐频道数据一样,这里可以不用获取api和封装来渲染,直接选择父传子(点击高亮的效果也一样)。在主页面home/index.vue中父传子实现我的频道标签显示,在channel-edit中接收
   <ChannelEdit :mychannels="channels"/>
   props: {
    mychannels: {
      type: Array,
      require: true
    }
  }
渲染我的频道页面
           <van-grid :gutter="10">
            <van-grid-item class="grid-item"
                v-for="(channels,index) in mychannels"
                :key="index"
                icon="clear"
                text="channels.name" />
            </van-grid>
高亮效果:
<van-grid :gutter="10">
            <van-grid-item class="grid-item"
                v-for="(channels,index) in mychannels"
                :key="index"
                icon="clear"
                />
                <!-- v-bind:class
                一个对象,对象中的key表示作用的CSS类名 对象的value要计算出布尔值
            true,作用类名 false不作用-->
                <span class="text"
                :class="{active: index === active}">{{channels.name}}</span>
            </van-grid>



    props: {
    mychannels: {
      type: Array,
      require: true
    },
    myactive: {
      type: Number,
      require: true
    }
  }

4、展示推荐频道

先获取所有频道(由于没有推荐频道的接口 我们可以采用所有频道-我的频道 )
  computed: {
    recommendChannels () {
      return this.allchannels.filter(channel => {
        return !this.mychannels.find(mychannel => {
          return mychannel.id === channel.id
        })
      })
    }
  }
接着渲染页面:
<!-- 频道推荐 -->
        <div class="channel-recommand">
            <van-cell style="font-size: medium;" title="频道推荐"  />
            <van-grid :gutter="10" >
            <van-grid-item class="grid-item"
                v-for="(channel,id) in recommendChannels"
                :key="id"
                icon="plus"
                :text="channel.name"
                />
            </van-grid>
        </div>

5、添加频道

频道推荐绑定点击事件
@click="onAddChannel(channel)
点击添加频道
onAddChannel (channel) {
      // this.$emit('mychannels', channel)
      //this.mychannels.push(channel) 这里报错了:无法在props属性中直接修改数组所以尝试下面的方法
      this.localmyChannel.push(channel)
      //console.log(channel.name)
    }
    
在data中修改props值的方法之一
  //  
   data: function () {
    return {
      localmyChannel: this.mychannels,
      allchannels: []
    }
  }
至此成功实现添加频道功能

6、频道处理编辑状态

把删除按钮重新设置属性,给类名来控制样式
<van-icon slot="icon" name="clear"></van-icon>
在data中声明变量isEdit控制编辑状态的显示,通过v-show来控制显示删除按钮与否
isEdit: false


<van-icon v-show="isEdit"
slot="icon" name="clear"></van-icon>
在我的频道的编辑按钮绑定点击事件
        <van-cell>
            <div slot="title">我的频道</div>
            <van-button round size="mini"
                type="danger"
                style="margin-top: 10px;"
                @click="isEdit=!isEdit"
                plain>{{isEdit?'完成':'编辑'}}</van-button>
        </van-cell>
最后设置默认的推荐频道不允许被删除,在data属性中添加固定频道
fixChannel: [0]


//再判断一次便可以实现该功能了 
<van-icon v-show="isEdit&&!fixChannel.includes(channel.id)"
            slot="icon" name="clear"></van-icon>

7、删除频道

定义一个删除函数onDelete
<van-grid-item class="grid-item"
                v-for="(channel,index) in mychannels"
                :key="index"
                :text="channel.name"
                @click="onDelete(channel,index)"
                >
点击删除,这里要删除mychannels,但是mychannels在props里,所以定义了localmyChannel: this.mychannels
onDelete (channel, index) {
      console.log(channel, index)
      // 判断显示完成按钮就是可删除状态,否则就是点击跳转
      if (this.isEdit) {
        this.localmyChannel.splice(index, 1)
      } else {
        this.$emit('updateactive', index)
      }
8、点击跳转频道
无法在在props里改变active的值,在子组件里$emit()传参,在父组件中定义接收updateactive事件
@updateactive="onUpdateactive"
//定义事件
onUpdateactive (index) {
  // console.log(index)
  this.active = index
  this.isChannelEditShow = false
}

9、数据持久化

添加数据频道持久化

// 添加用户频道
export const AddUserChannels = channel => {
  return request({
    method: 'PATCH',
    url: '/v1_0/user/channels',
    data: {
      channels: [channel]
    }
  })
}

async onAddChannel (channel) {
  // this.$emit('mychannels', channel)
  // 没有登录,存储到本地
  this.localmyChannel.push(channel)
  if (this.user) {
    try {
      await AddUserChannels({
        id: channel.id,
        seq: this.mychannels.length
      })
    } catch (err) {
      this.$toast('保存失败')
    }
  } else {
    setItem('TOUTIAO-Channel', channel)
  }
}

删除数据持久化

// 删除用户频道
export const DeleteUserChannels = channelId => {
  return request({
    method: 'DELETE',
    url: `/v1_0/user/channels/${channelId}`
  })
}

onDelete (channel, index) {
  console.log(channel, index)
  // 判断显示完成按钮就是可删除状态,否则就是点击跳转
  if (this.isEdit) {
    this.localmyChannel.splice(index, 1)
    // this.localmyChannel.splice(index, 1)
    if (index <= this.active) {
      this.$emit('updateactive', this.active - 1, true)
    }
    this.deleteChannel(channel)
  } else {
    this.$emit('updateactive', index)
  }
},
async deleteChannel (channel) {
  try {
    if (this.user) {
    // 已经登录,数据更新到线上
      await DeleteUserChannels(channel.id)
    } else {
      // 未登录
      setItem('TOUTIAO-Channel', this.mychannels)
    }
  } catch (err) {
    this.$toast('请求失败,稍后重试')
  }
}