首先简单介绍一下项目。
项目简单的分为了首页和个人中心两大板块,在首页中我们可以快速浏览文章信息,也可以点击搜索,搜索我们感兴趣的文章内容;可以滑动标签页选择自己感兴趣的频道进行浏览。在个人中心页中,能够进行个人信息的修改以及退出登录操作。
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、点击切换标签页,换至不同的频道