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

首先简单介绍一下项目。

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

 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、点击切换标签页,换至不同的频道

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340