头条新闻(Vue实战项目)-首页1

发布于:2023-01-06 ⋅ 阅读:(458) ⋅ 点赞:(0)

首先简单介绍一下项目。

 项目简单的分为了首页和个人中心两大板块,在首页中我们可以快速浏览文章信息,也可以点击搜索,搜索我们感兴趣的文章内容;可以滑动标签页选择自己感兴趣的频道进行浏览。在个人中心页中,能够进行个人信息的修改以及退出登录操作。

 1.项目整体布局

Layout文件 

<template>
  <div>
    <router-view></router-view>
    <van-tabbar route>
  <van-tabbar-item replace to="/layout/home" icon="home-o">首页</van-tabbar-item>
  <van-tabbar-item replace to="/layout/user" icon="user-circle-o">个人中心</van-tabbar-item>
</van-tabbar>
  </div>
</template>

<script>
import Vue from 'vue'
import { Tabbar, TabbarItem } from 'vant'

Vue.use(Tabbar)
Vue.use(TabbarItem)
export default {

}
</script>

<style>

</style>

首页头部导航栏

<template>
  <div>
    <van-nav-bar fixed>
  <template #right>
    <van-icon name="search" size="0.48rem" @click="jump"/>
  </template>
  <template #left>
  <van-icon name="https://img1.baidu.com/it/u=2838100141,2488760005&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=507" size="18" />
  <span>我的头条</span>
  </template>
</van-nav-bar>
<MyTab/>
</div>
</template>

<script>
import Vue from 'vue'
import { NavBar, Icon } from 'vant'
import MyTab from './components/MyTab.vue'
Vue.use(NavBar)
Vue.use(Icon)
export default {
  components: {
    MyTab
  },
  methods: {
    jump () {
      this.$router.push('/search')
      console.log(this)
    }
  }
}
</script>

<style scoped>
span{
color:#fff;
}
</style>

 频道切换的标签页

<template>
  <div class="box">
  <van-tabs  sticky offset-top="1.22667rem" v-model="channelId" @change="channelChange">
  <van-tab v-for="item in userChannelList" :title="item.name" :key="item.id" :name="item.id" >
   <ArticleList :channelId="channelId"/>
  </van-tab>
  <van-icon name="plus" size="0.3733334rem" class="moreChannels" @click="click"/>
</van-tabs>
<!-- 频道管理弹出层 -->
<van-popup v-model="show" get-container="body" class="channel">
  <!-- 给子组件传递两个list -->
   <ChannelEdit :userlist="userChannelList" :uncheckList="uncheckList"
   @addChannel="addChannel"
   @remove="remove"
   @close="close"
   v-model="channelId"
   />
</van-popup>
  </div>
</template>
// channelId当前的选项卡 channelChange标签页切换事件
<script>
import Vue from 'vue'
import { Tab, Tabs, Popup } from 'vant'
import { getUserChannelAPI, getAllChannelsAPI, resetUserChannel, deleteChannel } from '@/api/index'
import ArticleList from './ArticleList.vue'
import ChannelEdit from '../channelEdit.vue'
Vue.use(Tab)
Vue.use(Popup)
Vue.use(Tabs)
export default {
  data () {
    return {
      userChannelList: [], // 用户频道列表
      channelId: 0, // 默认选中第一个
      show: false,
      allChannelList: [] // 所有频道列表
    }
  },
  async created () {
    const res = await getUserChannelAPI()
    this.userChannelList = res.data.data.channels
    const res1 = await getAllChannelsAPI()
    this.allChannelList = res1.data.data.channels
  },
  components: {
    ArticleList,
    ChannelEdit
  },
  methods: {
    async channelChange () {
    (new Date()).getTime() })
    },
    click () {
      this.show = true
    },
    async  addChannel (channelObj) {
      this.userChannelList.push(channelObj)
      const newArr = this.userChannelList.filter(item => item.id !== 0)
      const theNewArr = newArr.map((item, index) => { // 返回一个添加后的新数组
        const newObj = { ...item } // 浅拷贝
        delete newObj.name
        newObj.seq = index
        return newObj
      })
      const res = await resetUserChannel({
        channels: theNewArr
      })
      console.log(res)
    },
    async remove (channelObj) {
      const index = this.userChannelList.findIndex(obj => obj.id === channelObj.id)
      this.userChannelList.splice(index, 1)
      // 调用删除接口
      const res = await deleteChannel({
        channelId: channelObj.id
      })
      console.log(res)
    },
    close () {
      this.show = false
    }
  },
  computed: {
    // 计算获得用户未选的频道
    uncheckList () {
      const newArr = this.allChannelList.filter(largeObj => {
        const index = this.userChannelList.findIndex(smallObj => {
          return smallObj.id === largeObj.id // 判断两个id是否相等
        })
        if (index > -1) { // 说明存在两个相等的
          return false // 返回假,不存入新数组
        } else {
          return true // 返回真,存入新数组
        }
      })
      return newArr // 返回想要的新数组
    }
  }
}
</script>

<style scoped lang="less">
/* .box{
padding-top:1.2432rem;
} */
/deep/.van-tabs__content{
    padding-top: 44px;
}
.moreChannels{
  position:fixed;
  top:62px;
  right: 8px;
  z-index:999
}
.channel{
  width:100vw;
  height:100vh;
}
</style>

文章列表展示

<template>
  <!-- 文章列表 -->
  <div>
<van-pull-refresh v-model="isLoading" @refresh="onRefresh">
    <van-list
  v-model="loading"
  :finished="finished"
  finished-text="没有更多了"
  offset="50"
  :immediate-check = "false"
  @load="onLoad"
  >
 <!-- :immediate-check = "false"        // 解决重复key的问题,一上来不要发送请求
 解决方法2 在onload方法中第一行写一个数组长度为零的判断
 -->
  <ArticleItem
   v-for="item in list"
    :key="item.art_id"
     :artObj="item"
       @dislike="dislike"
        @report="report"
        @click.native="click(item.art_id)"
        />
        // .native修饰符代表了是原生的点击事件
  </van-list>
  </van-pull-refresh>
  </div>
</template>

<script>
import ArticleItem from '../../../components/ArticleItem.vue'
import { getArticleListAPI, dislikeListAPI, articleReportAPI } from '@/api/index'
import Vue from 'vue'
import { List, Notify, PullRefresh } from 'vant'

Vue.use(List)
Vue.use(PullRefresh)
Vue.use(Notify)
export default {
  components: {
    ArticleItem
  },
  props: {
    // list: Array // 文章列表数组
    channelId: Number // 频道ID
  },
  data () {
    return {
      finished: false, // 完成状态
      loading: false, // 加载状态
      list: [],
      theTime: new Date().getTime(), // 用于分页
      isLoading: false // 顶部加载状态
    }
  },
  methods: {
    // 加载方法
    async  onLoad () {
      // if (this.list.length === 0) { return }
      this.getArticle()
      const res = await getArticleListAPI({ channel_id: this.channelId, timestamp: this.theTime })
      this.list = [...this.list, ...res.data.data.results]
      this.theTime = res.data.data.pre_timestamp
    },
    async onRefresh () { // 刷新相当于重新请求接口
      this.list = []
      this.theTime = new Date().getTime()
      this.getArticle()
    },
    // 专门负责发送请求
    async getArticle () {
      const res = await getArticleListAPI({ channel_id: this.channelId, timestamp: this.theTime })
      this.list = [...this.list, ...res.data.data.results]
      this.theTime = res.data.data.pre_timestamp
      this.loading = false // 触发下一次加载
      if (res.data.data.pre_timestamp === null) {
        this.finished = true
      }
      // 顶部加载状态改为false
      this.isLoading = false
    },
    // 不感兴趣列表
    async  dislike (id) {
      await dislikeListAPI({
        artId: id
      })
      Notify({ type: 'success', message: '反馈成功' })
    },
    // 举报文章列表
    async report (id, value) {
      await articleReportAPI({
        artId: id,
        type: value
      })
      Notify({ type: 'success', message: '举报成功' })
    },
    // 跳转到文章详情页
    click (id) {
      this.$router.push({
        path: `/detail?art_id=${id}`
      })
    }
  },
  created () {
    this.getArticle()
  }
}
</script>

<style>

</style>

在这里将文章的每个item进行了分离,组成了另外一个组件。方便后续组件复用。

<template>
 <div>
   <!-- 一条文章单元格 -->
  <van-cell>
    <!-- 标题区域的插槽 -->
    <template #title>
      <div class="title-box">
        <!-- 标题 -->
        <span>{{ artObj.title }}</span>
        <!-- 单图 -->
        <img v-if="artObj.cover.type===1" class="thumb" :src="artObj.cover.images" />
      </div>
      <!-- 三张图片 -->
      <div class="thumb-box" v-if="artObj.cover.type>1">
        <img class="thumb" :src="imgUrl" v-for="(imgUrl,index) in artObj.cover.images" :key="index"/>
      </div>
    </template>
    <!-- label 区域的插槽 -->
    <template #label>
      <div class="label-box">
        <div>
          <span>{{ artObj.aut_name }}</span>
          <span>{{ artObj.comm_count }} 评论</span>
          <span>{{ formatTime(artObj.pubdate) }}</span>
        </div>
        <!-- 反馈按钮 -->
        <van-icon name="cross" @click.stop="show = true" v-if="isShow"/>
      </div>
    </template>
  </van-cell>
  <van-action-sheet
  v-model="show"
   :actions="actions"
    @select="onSelect"
     get-container="body"
      :cancel-text="bottomText"
      @cancel ="cancelFn"
      @close="closeFn"
      />
 </div>
</template>

<script>
import Vue from 'vue'
import { Cell, CellGroup, Tag, ActionSheet, Toast } from 'vant'
import { timeAgo } from '@/utils/date'
import { firstActions, secondActions } from '@/api/report'
Vue.use(Cell)
Vue.use(Tag)
Vue.use(CellGroup)
Vue.use(ActionSheet)
Vue.use(Toast)
export default {
  props: {
    artObj: Object,
    isShow: {
      type: Boolean,
      default: true
    } // 文章对象
  },
  methods: {
    formatTime: timeAgo,
    onSelect (action) {
      // 默认情况下点击选项时不会自动收起
      // 可以通过 close-on-click-action 属性开启自动收起
      // this.show = false
      // 展示二级面板
      if (action.name === '反馈垃圾内容') {
        this.actions = secondActions
        this.bottomText = '返回'
      } else if (action.name === '不感兴趣') {
        this.$emit('dislike', this.artObj.art_id)
        this.show = false
      } else { // 二级面板反馈
        this.$emit('report', this.artObj.art_id, action.value)
        this.show = false
      }
    },
    cancelFn () {
      // 返回一级面板
      if (this.bottomText === '返回') {
        this.show = true
        this.actions = firstActions
        this.bottomText = '取消'
      }
    },
    // 关闭面板时的操作,让数据回到一级面板
    closeFn () {
      this.actions = firstActions
      this.bottomText = '取消'
    }
  },
  data () {
    return {
      show: false,
      actions: firstActions,
      bottomText: '取消'
    }
  }
}
</script>

<style scoped lang="less">


/* 单图 */
.thumb {
  width: 3.0541rem;
  height: 1.8919rem;
  background-color: #f8f8f8;
  object-fit: cover;
}
/* 三图 */
.thumb-box {
  display: flex;
  justify-content: space-between;
}
/* 标题样式 */
.title-box {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}

/* label描述样式 */
.label-box {
  display: flex;
  justify-content: space-between;
  align-items: center;
  // padding-bottom: 50px;
}

/* 文章信息span */
.label-box span {
  margin: 0 0.0811rem;
  &:first-child {
    margin-left: 0;
  }
}
</style>
 

首页效果展示之反馈文章内容

1、可以点击×对文章进行反馈

 

 

 

2、点击切换标签页,换至不同的频道

 

本文含有隐藏内容,请 开通VIP 后查看