uni-app实战之社区交友APP(4)首页开发

文章目录

  • 前言
  • 一、图文列表样式开发
    • 1.pages.json配置
    • 2.头像、昵称和关注按钮
    • 3.标题和互动按钮
    • 4.封装样式组件
  • 二、列表组件优化
    • 1.全局分割线开发
    • 2.动画特效
    • 3.关注功能完善
    • 4.顶踩功能
  • 三、滚动选项卡开发
    • 1.选项卡动态显示
    • 2.列表滑动实现
    • 3.列表显示和同步
    • 4.上拉加载开发
    • 5.封装上拉加载组件
    • 6.封装无数据默认组件
  • 总结

前言

本文主要介绍了首页图文列表和滚动选项卡的开发:
图文列表的开发,包括顶部导航栏配置,图文列表项(头像、昵称、关注按钮、标题、标题封面图、点赞、踩、评论和分享)等的开发;
列表组件优化,包括分割线的开发和封装,动画特效实现,关注、顶踩功能的完善;
滚动选项卡开发,包括顶部选项卡开发、列表的同步显示和滑动,上拉加载的开发和封装,无数据组件开发等。

一、图文列表样式开发

1.pages.json配置

删除之前创建的demo页面及其目录,之前在配置pages.json时,"pages"中并未设置style,此时需要设置"app-plus",即配置编译到 App 平台时的特定样式。

先下载发帖的图标,演示如下:

解压下载后的压缩包并将其中的iconfont.ttf复制到项目目录下的static目录下,并作为首页配置的app-plus>titleNView>buttons>fontSrc
同时在我的项目页面选择Unicode,并复制发帖icon的代码,例如,修改为uni-app支持的格式\ue668,作为app-plus>titleNView>buttons>text
关于app-plus的说明可参考文档https://uniapp.dcloud.io/collocation/pages?id=app-plus。

pages.json如下:

{
     
	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
		{
     
			"path": "pages/index/index",
			"style": {
     
				"app-plus": {
     
					// 导航栏配置
					"titleNView": {
     
						// 搜索框配置
						"searchInput": {
     
							"align":"center",
							"backgroundColor":"#F5F4F2",
							"borderRadius":"4px",
							"disabled": true,
							"placeholder": "搜索帖子",
							"placeholderColor": "#6D6C67"
						},
						// 按钮设置
						"buttons": [
							{
     
								"color":"#333333",
								"colorPressed":"#FD597C",
								"float":"right",
								"fontSize":"20px",
								"fontSrc":"/static/iconfont.ttf",
								"text": "\ue668"
							}
						]
					}
				}
			}
		}
	    ,{
     
            "path" : "pages/news/news",
            "style" :                                                                                    
            {
     
                "navigationBarTitleText": "",
                "enablePullDownRefresh": false
            }
            
        }
        ,{
     
            "path" : "pages/msg/msg",
            "style" :                                                                                    
            {
     
                "navigationBarTitleText": "",
                "enablePullDownRefresh": false
            }
            
        }
        ,{
     
            "path" : "pages/my/my",
            "style" :                                                                                    
            {
     
                "navigationBarTitleText": "",
                "enablePullDownRefresh": false
            }
            
        }
        
    ],
	"globalStyle": {
     
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "Community Dating",
		"navigationBarBackgroundColor": "#FFFFFF",
		"backgroundColor": "#FFFFFF"
	},
	"tabBar": {
     
		"color":"#323232",
		"selectedColor":"#ED6384",
		"backgroundColor":"#FFFFFF",
		"borderStyle": "black",
		"list": [
			{
     
				"pagePath": "pages/index/index",
				"text": "首页",
				"iconPath": "static/tabbar/index.png",
				"selectedIconPath": "static/tabbar/indexed.png"
			},
			{
     
				"pagePath": "pages/news/news",
				"text": "动态",
				"iconPath": "static/tabbar/news.png",
				"selectedIconPath": "static/tabbar/newsed.png"
			},
			{
     
				"pagePath": "pages/msg/msg",
				"text": "消息",
				"iconPath": "static/tabbar/paper.png",
				"selectedIconPath": "static/tabbar/papered.png"
			},
			{
     
				"pagePath": "pages/my/my",
				"text": "我的",
				"iconPath": "static/tabbar/home.png",
				"selectedIconPath": "static/tabbar/homed.png"
			}
		]
	}
}

显示:
uni-app实战之社区交友APP(4)首页开发_第1张图片

可以看到,定义出了搜索框和发帖按钮,点击按钮会改变颜色。

2.头像、昵称和关注按钮

uni-app 支持的通用 css 单位包括 px、rpx:

  • px
    即屏幕像素。
  • rpx
    即响应式px,一种根据屏幕宽度自适应的动态单位。以750宽的屏幕为基准,750rpx恰好为屏幕宽度。

开发者可以通过设计稿基准宽度计算页面元素 rpx 值,设计稿 1px 与框架样式 1rpx 转换公式为设计稿 1px / 设计稿基准宽度 = 框架样式 1rpx / 750rpx,所以页面元素宽度在 uni-app 中的宽度计算公式为750 * 元素在设计稿中的宽度 / 设计稿基准宽度
更多可参考https://uniapp.dcloud.io/frame?id=尺寸单位。

index.vue页面设置头像显示如下:

<template>
	<view>
		<image src="/static/img/userpic/12.jpg" style="width: 65rpx; height: 65rpx;">image>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      

			}
		},
		onLoad() {
      

		},
		methods: {
      

		}
	}
script>

<style>

style>

显示:
uni-app实战之社区交友APP(4)首页开发_第2张图片

在不同尺寸的设备上,其大小会自动缩放。

如需直接使用样式和素材等文件,可以直接点击加QQ群 Python极客部落963624318 ,在群文件夹uni-app实战之社区交友APP中下载即可。

先实现图文列表的第一部分,即头像、昵称和关注按钮,如下:

<template>
	<view>
		
		<view style="padding: 20rpx;">
			
			<view style="display: flex; align-items: center; justify-content: space-between;">
				<view style="display: flex; align-items: center;">
					
					<image src="/static/img/userpic/12.jpg" mode="" style="width: 65rpx; height: 65rpx; border-radius: 100%; margin-right: 20rpx;" lazy-load>image>
					
					<view>
						<view style="font-size: 30rpx; line-height: 1.5;">Corleyview>
						<text style="color: #9D9589; font-size: 25rpx; line-height: 1.5;">2021-01-24 上午11:20text>
					view>
				view>
				
				<view style="width: 90rpx; height: 50rpx; display: flex; align-items: center; justify-content: center; border-radius: 5rpx; background-color: #FF4A6A; color: #FFFFFF;">
					关注
				view>
			view>
			
			<view>view>
			
			<view>view>
			
			<view>view>
		view>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      

			}
		},
		onLoad() {
      

		},
		methods: {
      

		}
	}
script>

<style>

style>

显示:
uni-app实战之社区交友APP(4)首页开发_第3张图片

显然,实现了头像、昵称、发布日期和关注按钮的展示。

3.标题和互动按钮

现进一步实现列表项的第二部分,即发帖标题、点赞、评论和分享等。

先按照之前的方法在https://www.iconfont.cn/中添加点赞、踩、评论和分享的图标,再将iconfont.css更新至common/icon.css中。

index.vue如下:

<template>
	<view>
		
		<view style="padding: 20rpx;">
			
			<view style="display: flex; align-items: center; justify-content: space-between;">
				<view style="display: flex; align-items: center;">
					
					<image src="/static/img/userpic/12.jpg" mode="" style="width: 65rpx; height: 65rpx; border-radius: 100%; margin-right: 20rpx;"
					 lazy-load>image>
					
					<view>
						<view style="font-size: 30rpx; line-height: 1.5;">Corleyview>
						<text style="color: #9D9589; font-size: 25rpx; line-height: 1.5;">2021-01-24 上午11:20text>
					view>
				view>
				
				<view style="width: 90rpx; height: 50rpx; display: flex; align-items: center; justify-content: center; border-radius: 5rpx; background-color: #FF4A6A; color: #FFFFFF;">
					关注
				view>
			view>
			
			<view style="font-size: 30rpx; margin: 10rpx 0;">
				uni-app入门教程
			view>
			
			<image src="/static/img/datapic/42.jpg" style="height: 350rpx; width: 100%; border-radius: 5rpx;">image>
			
			<view style="display: flex; align-items: center;">
				<view style="flex: 1; display: flex; align-items: center; justify-content: center;">
					<text class="iconfont icon-dianzan" style="margin-right: 20rpx;">text>
					<text>1text>
				view>
				<view style="flex: 1; display: flex; align-items: center; justify-content: center;">
					<text class="iconfont icon-cai" style="margin-right: 20rpx;">text>
					<text>1text>
				view>
				<view style="flex: 1; display: flex; align-items: center; justify-content: center;">
					<text class="iconfont icon-pinglun" style="margin-right: 20rpx;">text>
					<text>1text>
				view>
				<view style="flex: 1; display: flex; align-items: center; justify-content: center;">
					<text class="iconfont icon-fenxiang" style="margin-right: 20rpx;">text>
					<text>1text>
				view>
			view>
		view>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      

			}
		},
		onLoad() {
      

		},
		methods: {
      

		}
	}
script>

<style>

style>

显示:
uni-app实战之社区交友APP(4)首页开发_第4张图片

可以看到,已经实现了一个列表项的基本内容。

4.封装样式组件

虽然已经实现了列表,但是可以看到代码很冗余、有大量CSS重复代码,同时列表可以复用,也可以封装成组件,来提高代码的复用率
先优化项目代码,将CSS样式提取到公共的CSS文件中。
common下新建文件base.css,保存公共样式如下:

/* 内外边距 */
.p-2 {
     
	padding: 20rpx;
}

/* flex布局 */
.flex {
     
	display: flex;
}
.align-center {
     
	align-items: center;
}
.justify-between {
     
	justify-content: space-between;
}
.justify-center {
     
	justify-content: center;
}
.flex-1 {
     
	flex: 1;
}

/* 圆角 */
.rounded-circle {
     
	border-radius: 100%;
}
.rounded {
     
	border-radius: 8rpx;
}

/* margin */
.mr-2 {
     
	margin-right: 20rpx;
}
.my-1 {
     
	margin-top: 10rpx;
	margin-bottom: 0rpx;
}

/* 字体 */
.font-md {
     
	font-size: 35rpx;
}
.font {
     
	font-size: 30rpx;
}
.font-sm {
     
	font-size: 25rpx;
}

/* 文字颜色 */
.text-white {
     
	color: #FFFFFF;
}
.text-light-muted {
     
	color: #A9A5A0; 
}

/* 宽度 */
/* #ifndef APP-PLUS-NVUE */
.w-100 {
     
	width: 100%;
}
/* #endif */

base.css文件不仅可以应用于该项目,也可以应用于其他uni-app项目。

common目录下新建common.css保存本项目全局样式,如下:

/* 本项目全局样式 */
.bg-main {
     
	background-color: #FF4A6A;
}

App.vue中导入CSS文件,如下:

<script>
	export default {
      
		onLaunch: function() {
      
			console.log('App Launch')
		},
		onShow: function() {
      
			console.log('App Show')
		},
		onHide: function() {
      
			console.log('App Hide')
		}
	}
script>

<style>
	/*每个页面公共css */
	/* 官方CSS库 */
	@import url("./common/uni.css");
	/* 自定义图标库 */
	@import url("./common/icon.css");
	/* 动画库 */
	@import url("./common/animate.css");
	/* 自定义样式库 */
	@import url("./common/base.css");
	/* 全局样式 */
	@import url("./common/common.css");
style>

index.vue简化如下:

<template>
	<view>
		
		<view class="p-2">
			
			<view class="flex align-center justify-between">
				<view class="flex align-center">
					
					<image class="rounded-circle mr-2" src="/static/img/userpic/12.jpg" mode="" style="width: 65rpx; height: 65rpx;"
					 lazy-load>image>
					
					<view>
						<view class="font" style="line-height: 1.5;">Corleyview>
						<text class="font-sm text-light-muted" style="line-height: 1.5;">2021-01-24 上午11:20text>
					view>
				view>
				
				<view class="flex align-center justify-center rounded bg-main text-white" style="width: 90rpx; height: 50rpx;">
					关注
				view>
			view>
			
			<view class="font my-1">
				uni-app入门教程
			view>
			
			<image class="rounded w-100" src="/static/img/datapic/42.jpg" style="height: 350rpx;">image>
			
			<view class="flex align-center">
				<view class="flex align-center justify-center flex-1">
					<text class="iconfont icon-dianzan mr-2">text>
					<text>1text>
				view>
				<view class="flex align-center justify-center flex-1">
					<text class="iconfont icon-cai mr-2">text>
					<text>1text>
				view>
				<view class="flex align-center justify-center flex-1">
					<text class="iconfont icon-pinglun mr-2">text>
					<text>1text>
				view>
				<view class="flex align-center justify-center flex-1">
					<text class="iconfont icon-fenxiang mr-2">text>
					<text>1text>
				view>
			view>
		view>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      

			}
		},
		onLoad() {
      

		},
		methods: {
      

		}
	}
script>

<style>

style>

显然,此时代码更加简洁美观,并且可以达到与之前同样的效果。

现进一步实现将列表项封装为组件
首先替换数据、实现列表渲染,如下:

<template>
	<view>
		<block v-for="(item, index) in list" :key="index">
			
			<view class="p-2">
				
				<view class="flex align-center justify-between">
					<view class="flex align-center">
						
						<image class="rounded-circle mr-2" :src="item.userpic" mode="" style="width: 65rpx; height: 65rpx;"
						 lazy-load>image>
						
						<view>
							<view class="font" style="line-height: 1.5;">{
    {item.username}}view>
							<text class="font-sm text-light-muted" style="line-height: 1.5;">{
    {item.newstime}}text>
						view>
					view>
					
					<view class="flex align-center justify-center rounded bg-main text-white" style="width: 90rpx; height: 50rpx;">
						{
    {item.isFollow?'已关注':'关注'}}
					view>
				view>
				
				<view class="font my-1">
					{
    {item.title}}
				view>
				
				<image class="rounded w-100" :src="item.titlepic" style="height: 350rpx;">image>
				
				<view class="flex align-center">
					<view class="flex align-center justify-center flex-1">
						<text class="iconfont icon-dianzan mr-2">text>
						<text>{
    {item.support.support_count}}text>
					view>
					<view class="flex align-center justify-center flex-1">
						<text class="iconfont icon-cai mr-2">text>
						<text>{
    {item.support.unsupport_count}}text>
					view>
					<view class="flex align-center justify-center flex-1">
						<text class="iconfont icon-pinglun mr-2">text>
						<text>{
    {item.comment_count}}text>
					view>
					<view class="flex align-center justify-center flex-1">
						<text class="iconfont icon-fenxiang mr-2">text>
						<text>{
    {item.share_count}}text>
					view>
				view>
			view>
		block>

	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      
				list: [
					{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support",
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				]
			}
		},
		onLoad() {
      

		},
		methods: {
      

		}
	}
script>

<style>

style>

显示:

显然,已经实现了列表渲染。

再实现封装到组件,项目下新建components目录,下新建common目录,下建common-list.vue作为列表组件,如下:

<template>
	<view class="p-2">
		
		<view class="flex align-center justify-between">
			<view class="flex align-center">
				
				<image class="rounded-circle mr-2" :src="item.userpic" mode="" style="width: 65rpx; height: 65rpx;"
				 lazy-load>image>
				
				<view>
					<view class="font" style="line-height: 1.5;">{
    {item.username}}view>
					<text class="font-sm text-light-muted" style="line-height: 1.5;">{
    {item.newstime}}text>
				view>
			view>
			
			<view class="flex align-center justify-center rounded bg-main text-white" style="width: 90rpx; height: 50rpx;">
				{
    {item.isFollow?'已关注':'关注'}}
			view>
		view>
		
		<view class="font my-1">
			{
    {item.title}}
		view>
		
		<image class="rounded w-100" :src="item.titlepic" style="height: 350rpx;">image>
		
		<view class="flex align-center">
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-dianzan mr-2">text>
				<text>{
    {item.support.support_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-cai mr-2">text>
				<text>{
    {item.support.unsupport_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-pinglun mr-2">text>
				<text>{
    {item.comment_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-fenxiang mr-2">text>
				<text>{
    {item.share_count}}text>
			view>
		view>
	view>
template>

<script>
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
	}
script>

<style>
style>

index.vue中导入并使用组件即可,如下:

<template>
	<view>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index">common-list>
		block>

	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [
					{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support",
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				]
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      

		}
	}
script>

<style>

style>

可以达到与之前相同的效果。

二、列表组件优化

1.全局分割线开发

全局分割线也是以组件的形式添加。
先在components/common下新建divider.vue如下:

<template>
	<view style="height: 15rpx; background-color: #F5F5F4;">view>
template>

<script>
script>

<style>
style>

因为分割线可能在很多地方都会用到,所以可以添加到全局组件,main.js中添加如下:

import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

// 引入全局组件
import divider from './components/common/divider.vue';
Vue.component('divider', divider)

App.mpType = 'app'

const app = new Vue({
     
    ...App
})
app.$mount()

index.vue中无需导入、直接使用即可,如下:

<template>
	<view>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index">common-list>
			
			<divider>divider>
		block>

	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [
					{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support",
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				]
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      

		}
	}
script>

<style>

style>

显示:
uni-app实战之社区交友APP(4)首页开发_第5张图片

可以看到,有比较明显的分割线效果。

2.动画特效

现在进一步给列表组件添加动画特效。

因为有的帖子没有封面图,因此需要v-if进行判断,需要修改组件common-list.vue,如下:

<template>
	<view class="p-2">
		
		<view class="flex align-center justify-between">
			<view class="flex align-center">
				
				<image class="rounded-circle mr-2" :src="item.userpic" mode="" style="width: 65rpx; height: 65rpx;"
				 lazy-load>image>
				
				<view>
					<view class="font" style="line-height: 1.5;">{
    {item.username}}view>
					<text class="font-sm text-light-muted" style="line-height: 1.5;">{
    {item.newstime}}text>
				view>
			view>
			
			<view class="flex align-center justify-center rounded bg-main text-white" style="width: 90rpx; height: 50rpx;">
				{
    {item.isFollow?'已关注':'关注'}}
			view>
		view>
		
		<view class="font my-1">
			{
    {item.title}}
		view>
		
		<image v-if="item.titlepic" class="rounded w-100" :src="item.titlepic" style="height: 350rpx;">image>
		
		<view class="flex align-center">
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-dianzan mr-2">text>
				<text>{
    {item.support.support_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-cai mr-2">text>
				<text>{
    {item.support.unsupport_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-pinglun mr-2">text>
				<text>{
    {item.comment_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-fenxiang mr-2">text>
				<text>{
    {item.share_count}}text>
			view>
		view>
	view>
template>

<script>
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
	}
script>

<style>
style>

index.vue增加测试数据,如下:

<template>
	<view>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index">common-list>
			
			<divider>divider>
		block>

	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [
					{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support",
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Brittany",
						userpic: "/static/img/userpic/16.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: false,
						title: "商业数据分析从入门到入职",
						support: {
      
							type: "support",
							support_count: 2,
							unsupport_count: 0
						},
						comment_count: 5,
						share_count: 1
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午14:44",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				]
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      

		}
	}
script>

<style>

style>

显示:

可以看到,没有封面图片,也能正常显示。

此时再实现点击关注按钮、添加动画特效,common-list.vue如下:

<template>
	<view class="p-2">
		
		<view class="flex align-center justify-between">
			<view class="flex align-center">
				
				<image class="rounded-circle mr-2" :src="item.userpic" mode="" style="width: 65rpx; height: 65rpx;"
				 lazy-load>image>
				
				<view>
					<view class="font" style="line-height: 1.5;">{
    {item.username}}view>
					<text class="font-sm text-light-muted" style="line-height: 1.5;">{
    {item.newstime}}text>
				view>
			view>
			
			<view class="flex align-center justify-center rounded bg-main text-white animate__animated animate__faster" hover-class="animate__jello" style="width: 90rpx; height: 50rpx;">
				{
    {item.isFollow?'已关注':'关注'}}
			view>
		view>
		
		<view class="font my-1">
			{
    {item.title}}
		view>
		
		<image v-if="item.titlepic" class="rounded w-100" :src="item.titlepic" style="height: 350rpx;">image>
		
		<view class="flex align-center">
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-dianzan mr-2">text>
				<text>{
    {item.support.support_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-cai mr-2">text>
				<text>{
    {item.support.unsupport_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-pinglun mr-2">text>
				<text>{
    {item.comment_count}}text>
			view>
			<view class="flex align-center justify-center flex-1">
				<text class="iconfont icon-fenxiang mr-2">text>
				<text>{
    {item.share_count}}text>
			view>
		view>
	view>
template>

<script>
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
	}
script>

<style>
style>

显示:

可以看到,点击关注按钮时,已实现了动画效果。

现进一步实现4个图标的特效,如下:

<template>
	<view class="p-2">
		
		<view class="flex align-center justify-between">
			<view class="flex align-center">
				
				<image class="rounded-circle mr-2" :src="item.userpic" mode="" style="width: 65rpx; height: 65rpx;"
				 lazy-load>image>
				
				<view>
					<view class="font" style="line-height: 1.5;">{
    {item.username}}view>
					<text class="font-sm text-light-muted" style="line-height: 1.5;">{
    {item.newstime}}text>
				view>
			view>
			
			<view class="flex align-center justify-center rounded bg-main text-white animate__animated animate__faster" hover-class="animate__jello" style="width: 90rpx; height: 50rpx;">
				{
    {item.isFollow?'已关注':'关注'}}
			view>
		view>
		
		<view class="font my-1">
			{
    {item.title}}
		view>
		
		<image v-if="item.titlepic" class="rounded w-100" :src="item.titlepic" style="height: 350rpx;">image>
		
		<view class="flex align-center">
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main">
				<text class="iconfont icon-dianzan mr-2">text>
				<text>{
    {item.support.support_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main">
				<text class="iconfont icon-cai mr-2">text>
				<text>{
    {item.support.unsupport_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main">
				<text class="iconfont icon-pinglun mr-2">text>
				<text>{
    {item.comment_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main">
				<text class="iconfont icon-fenxiang mr-2">text>
				<text>{
    {item.share_count}}text>
			view>
		view>
	view>
template>

<script>
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
	}
script>

<style>
style>

common.css如下:

/* 本项目全局样式 */
/* 背景 */
.bg-main {
     
	background-color: #FF4A6A;
}

/* 文本颜色 */
.text-main {
     
	color: #FF4A6A;
}

显示:

显然,也实现了动画效果。

3.关注功能完善

common-list组件需要定义接口,点击头像可以进入个人空间,点击关注按钮可以真正进行关注操作,点击点赞和踩实现数据更新,点击评论、转发、标题和图片等跳转到详情页,一般可以通过绑定事件实现。

common-list.vue组件绑定事件实现接口如下:

<template>
	<view class="p-2">
		
		<view class="flex align-center justify-between">
			<view class="flex align-center">
				
				<image class="rounded-circle mr-2" :src="item.userpic" mode="" style="width: 65rpx; height: 65rpx;" @click="openSpace"
				 lazy-load>image>
				
				<view>
					<view class="font" style="line-height: 1.5;">{
    {item.username}}view>
					<text class="font-sm text-light-muted" style="line-height: 1.5;">{
    {item.newstime}}text>
				view>
			view>
			
			<view class="flex align-center justify-center rounded bg-main text-white animate__animated animate__faster" hover-class="animate__jello" style="width: 90rpx; height: 50rpx;" @click="follow">
				{
    {item.isFollow?'已关注':'关注'}}
			view>
		view>
		
		<view class="font my-1" @click="openDetail">
			{
    {item.title}}
		view>
		
		<image v-if="item.titlepic" class="rounded w-100" :src="item.titlepic" style="height: 350rpx;" @click="openDetail">image>
		
		<view class="flex align-center">
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main" @click="doSupport('support')">
				<text class="iconfont icon-dianzan mr-2">text>
				<text>{
    {item.support.support_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main" @click="doSupport('unsupport')">
				<text class="iconfont icon-cai mr-2">text>
				<text>{
    {item.support.unsupport_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main" @click="openDetail">
				<text class="iconfont icon-pinglun mr-2">text>
				<text>{
    {item.comment_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main" @click="openDetail">
				<text class="iconfont icon-fenxiang mr-2">text>
				<text>{
    {item.share_count}}text>
			view>
		view>
	view>
template>

<script>
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
		methods: {
      
			// 打开个人空间
			openSpace(){
      
				console.log('Space opened');
			},
			// 关注
			follow(){
      
				console.log('Followed');
			},
			// 进入详情页
			openDetail(){
      
				console.log('Detail opened');
			},
			// 顶踩操作
			doSupport(type){
      
				console.log(type)
			}
		}
	}
script>

<style>
style>

显示:

可以看到,已经模拟出了接口操作。
现进一步实现关注功能,如下:

<template>
	<view class="p-2">
		
		<view class="flex align-center justify-between">
			<view class="flex align-center">
				
				<image class="rounded-circle mr-2" :src="item.userpic" mode="" style="width: 65rpx; height: 65rpx;" @click="openSpace"
				 lazy-load>image>
				
				<view>
					<view class="font" style="line-height: 1.5;">{
    {item.username}}view>
					<text class="font-sm text-light-muted" style="line-height: 1.5;">{
    {item.newstime}}text>
				view>
			view>
			
			<view v-if="!item.isFollow" class="flex align-center justify-center rounded bg-main text-white animate__animated animate__faster"
			 hover-class="animate__jello" style="width: 90rpx; height: 50rpx;" @click="follow">
				关注
			view>
		view>
		
		<view class="font my-1" @click="openDetail">
			{
    {item.title}}
		view>
		
		<image v-if="item.titlepic" class="rounded w-100" :src="item.titlepic" style="height: 350rpx;" @click="openDetail">image>
		
		<view class="flex align-center">
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main"
			 @click="doSupport('support')">
				<text class="iconfont icon-dianzan mr-2">text>
				<text>{
    {item.support.support_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main"
			 @click="doSupport('unsupport')">
				<text class="iconfont icon-cai mr-2">text>
				<text>{
    {item.support.unsupport_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main"
			 @click="openDetail">
				<text class="iconfont icon-pinglun mr-2">text>
				<text>{
    {item.comment_count}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main"
			 @click="openDetail">
				<text class="iconfont icon-fenxiang mr-2">text>
				<text>{
    {item.share_count}}text>
			view>
		view>
	view>
template>

<script>
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
		methods: {
      
			// 打开个人空间
			openSpace() {
      
				console.log('Space opened');
			},
			// 关注
			follow() {
      
				console.log('Followed');
				// 通知父组件
				this.$emit('follow', this.index);
			},
			// 进入详情页
			openDetail() {
      
				console.log('Detail opened');
			},
			// 顶踩操作
			doSupport(type) {
      
				console.log(type)
			}
		}
	}
script>

<style>
style>

父组件index.vue如下:

<template>
	<view>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index" @follow="follow">common-list>
			
			<divider>divider>
		block>
	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [
					{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support",
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Brittany",
						userpic: "/static/img/userpic/16.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: false,
						title: "商业数据分析从入门到入职",
						support: {
      
							type: "support",
							support_count: 2,
							unsupport_count: 0
						},
						comment_count: 5,
						share_count: 1
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午14:44",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				]
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      
			// 关注
			follow(e){
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注'+this.list[e].username+'成功'
				})
			}
		}
	}
script>

<style>

style>

可以看到,实现了子组件common-list向父组件index的消息传递。

显示:

显然,实现了正常的关注功能。

4.顶踩功能

现完善顶踩功能接口。
顶踩有3种状态:顶、踩或未操作,点击顶按钮后,对应数值加1,并且颜色变为激活状态。

common-list.vue如下:

<template>
	<view class="p-2">
		
		<view class="flex align-center justify-between">
			<view class="flex align-center">
				
				<image class="rounded-circle mr-2" :src="item.userpic" mode="" style="width: 65rpx; height: 65rpx;" @click="openSpace"
				 lazy-load>image>
				
				<view>
					<view class="font" style="line-height: 1.5;">{
    {item.username}}view>
					<text class="font-sm text-light-muted" style="line-height: 1.5;">{
    {item.newstime}}text>
				view>
			view>
			
			<view v-if="!item.isFollow" class="flex align-center justify-center rounded bg-main text-white animate__animated animate__faster"
			 hover-class="animate__jello" style="width: 90rpx; height: 50rpx;" @click="follow">
				关注
			view>
		view>
		
		<view class="font my-1" @click="openDetail">
			{
    {item.title}}
		view>
		
		<image v-if="item.titlepic" class="rounded w-100" :src="item.titlepic" style="height: 350rpx;" @click="openDetail">image>
		
		<view class="flex align-center">
			
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main"
			 @click="doSupport('support')" :class="item.support.type === 'support' ? 'support-active' : ''">
				<text class="iconfont icon-dianzan mr-2">text>
				<text>{
    {item.support.support_count > 0 ? item.support.support_count : '支持'}}text>
			view>
			
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main"
			 @click="doSupport('unsupport')" :class="item.support.type === 'unsupport' ? 'support-active' : ''">
				<text class="iconfont icon-cai mr-2">text>
				<text>{
    {item.support.unsupport_count > 0 ? item.support.unsupport_count : '反对'}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main"
			 @click="openDetail">
				<text class="iconfont icon-pinglun mr-2">text>
				<text>{
    {item.comment_count > 0 ? item.comment_count : '评论'}}text>
			view>
			<view class="flex align-center justify-center flex-1 animate__animated animate__faster" hover-class="animate__jello text-main"
			 @click="openDetail">
				<text class="iconfont icon-fenxiang mr-2">text>
				<text>{
    {item.share_count > 0 ? item.share_count : '分享'}}text>
			view>
		view>
	view>
template>

<script>
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
		methods: {
      
			// 打开个人空间
			openSpace() {
      
				console.log('Space opened');
			},
			// 关注
			follow() {
      
				console.log('Followed');
				// 通知父组件
				this.$emit('follow', this.index);
			},
			// 进入详情页
			openDetail() {
      
				console.log('Detail opened');
			},
			// 顶踩操作
			doSupport(type) {
      
				console.log(type);
				// 通知父组件
				this.$emit('doSupport', {
      
					type,
					index: this.index
				})
			}
		}
	}
script>

<style>
	.support-active {
      
		color: #FF4A6A;
	}
style>

index.vue如下:

<template>
	<view>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index" @follow="follow" @doSupport="doSupport">common-list>
			
			<divider>divider>
		block>
	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support", // 顶
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Brittany",
						userpic: "/static/img/userpic/16.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: false,
						title: "商业数据分析从入门到入职",
						support: {
      
							type: "unsupport", // 踩
							support_count: 2,
							unsupport_count: 3
						},
						comment_count: 5,
						share_count: 1
					},
					{
      
						username: "Jessica",
						userpic: "/static/img/userpic/7.jpg",
						newstime: "2021-01-24 下午14:44",
						isFollow: true,
						title: "Django+Vue开发生鲜电商平台",
						titlepic: "/static/img/datapic/11.jpg",
						support: {
      
							type: "", // 未操作
							support_count: 2,
							unsupport_count: 7
						},
						comment_count: 0,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午18:20",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				]
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			}
		}
	}
script>

<style>

style>

在父组件和子组件中进行了一系列优化,包括图标颜色变化,限制用户要么为顶要么为踩、并且只能踩顶一次、不能多次踩顶,次数为0时显示文本等。

显示:

显然,已经实现了顶踩的基本功能。

三、滚动选项卡开发

1.选项卡动态显示

滚动选项卡采用scroll-view组件实现,其scroll-into-view属性可以加速开发。

先实现滚动选项卡,base.css设置scroll-view样式如下:

/* 内外边距 */
.p-2 {
     
	padding: 20rpx;
}

/* flex布局 */
.flex {
     
	display: flex;
}

.align-center {
     
	align-items: center;
}

.justify-between {
     
	justify-content: space-between;
}

.justify-center {
     
	justify-content: center;
}

.flex-1 {
     
	flex: 1;
}

/* 圆角 */
.rounded-circle {
     
	border-radius: 100%;
}

.rounded {
     
	border-radius: 8rpx;
}

/* margin */
.mr-2 {
     
	margin-right: 20rpx;
}

.my-1 {
     
	margin-top: 10rpx;
	margin-bottom: 0rpx;
}

/* padding */
.px-5 {
     
	padding-left: 50rpx;
	padding-right: 50rpx;
}

.py-3 {
     
	padding-top: 30rpx;
	padding-bottom: 30rpx;
}

/* 字体 */
.font-md {
     
	font-size: 35rpx;
}

.font {
     
	font-size: 30rpx;
}

.font-sm {
     
	font-size: 25rpx;
}

/* 文字颜色 */
.text-white {
     
	color: #FFFFFF;
}

.text-light-muted {
     
	color: #A9A5A0;
}

/* 宽度 */
/* #ifndef APP-PLUS-NVUE */
.w-100 {
     
	width: 100%;
}

/* #endif */

/* scroll-view */
/* #ifndef APP-PLUS-NVUE */
.scroll-row {
     
	width: 100%;
	white-space: nowrap;
}

.scroll-row-item {
     
	display: inline-block !important;
}

/* #endif */

index.vue中增加滚动选项卡如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row">
			<view v-for="i in 20" :key="i" class="scroll-row-item px-5 py-3">{
    {i}}view>
		scroll-view>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index" @follow="follow" @doSupport="doSupport">common-list>
			
			<divider>divider>
		block>
	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support", // 顶
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Brittany",
						userpic: "/static/img/userpic/16.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: false,
						title: "商业数据分析从入门到入职",
						support: {
      
							type: "unsupport", // 踩
							support_count: 2,
							unsupport_count: 3
						},
						comment_count: 5,
						share_count: 1
					},
					{
      
						username: "Jessica",
						userpic: "/static/img/userpic/7.jpg",
						newstime: "2021-01-24 下午14:44",
						isFollow: true,
						title: "Django+Vue开发生鲜电商平台",
						titlepic: "/static/img/datapic/11.jpg",
						support: {
      
							type: "", // 未操作
							support_count: 2,
							unsupport_count: 7
						},
						comment_count: 0,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午18:20",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				]
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			}
		}
	}
script>

<style>

style>

显示:

可以看到,已经实现了滚动。

现实现选项卡内容显示,如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row ">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index" :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''">{
    {item.name}}view>
		scroll-view>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index" @follow="follow" @doSupport="doSupport">common-list>
			
			<divider>divider>
		block>
	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support", // 顶
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Brittany",
						userpic: "/static/img/userpic/16.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: false,
						title: "商业数据分析从入门到入职",
						support: {
      
							type: "unsupport", // 踩
							support_count: 2,
							unsupport_count: 3
						},
						comment_count: 5,
						share_count: 1
					},
					{
      
						username: "Jessica",
						userpic: "/static/img/userpic/7.jpg",
						newstime: "2021-01-24 下午14:44",
						isFollow: true,
						title: "Django+Vue开发生鲜电商平台",
						titlepic: "/static/img/datapic/11.jpg",
						support: {
      
							type: "", // 未操作
							support_count: 2,
							unsupport_count: 7
						},
						comment_count: 0,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午18:20",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					}
				],
				tabIndex: 0
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			}
		}
	}
script>

<style>

style>

base.css如下:

/* 内外边距 */
.p-2 {
     
	padding: 20rpx;
}

/* flex布局 */
.flex {
     
	display: flex;
}

.align-center {
     
	align-items: center;
}

.justify-between {
     
	justify-content: space-between;
}

.justify-center {
     
	justify-content: center;
}

.flex-1 {
     
	flex: 1;
}

/* 圆角 */
.rounded-circle {
     
	border-radius: 100%;
}

.rounded {
     
	border-radius: 8rpx;
}

/* margin */
.mr-2 {
     
	margin-right: 20rpx;
}

.my-1 {
     
	margin-top: 10rpx;
	margin-bottom: 0rpx;
}

/* padding */
.px-5 {
     
	padding-left: 50rpx;
	padding-right: 50rpx;
}

.px-3 {
     
	padding-left: 30rpx;
	padding-right: 30rpx;
}

.py-3 {
     
	padding-top: 30rpx;
	padding-bottom: 30rpx;
}

.py-2 {
     
	padding-top: 20rpx;
	padding-bottom: 20rpx;
}

/* 字体 */
.font-lg {
     
	font-size: 40rpx;
}

.font-md {
     
	font-size: 35rpx;
}

.font {
     
	font-size: 30rpx;
}

.font-sm {
     
	font-size: 25rpx;
}

.font-weight-bold {
     
	font-weight: bold;
}

/* 文字颜色 */
.text-white {
     
	color: #FFFFFF;
}

.text-light-muted {
     
	color: #A9A5A0;
}

/* 宽度 */
/* #ifndef APP-PLUS-NVUE */
.w-100 {
     
	width: 100%;
}

/* #endif */

/* scroll-view */
/* #ifndef APP-PLUS-NVUE */
.scroll-row {
     
	width: 100%;
	white-space: nowrap;
}

.scroll-row-item {
     
	display: inline-block !important;
}

/* #endif */

显示:
uni-app实战之社区交友APP(4)首页开发_第6张图片

再绑定事件实现动态滚动和动画显示的效果,如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index" @follow="follow" @doSupport="doSupport">common-list>
			
			<divider>divider>
		block>
	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support", // 顶
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Brittany",
						userpic: "/static/img/userpic/16.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: false,
						title: "商业数据分析从入门到入职",
						support: {
      
							type: "unsupport", // 踩
							support_count: 2,
							unsupport_count: 3
						},
						comment_count: 5,
						share_count: 1
					},
					{
      
						username: "Jessica",
						userpic: "/static/img/userpic/7.jpg",
						newstime: "2021-01-24 下午14:44",
						isFollow: true,
						title: "Django+Vue开发生鲜电商平台",
						titlepic: "/static/img/datapic/11.jpg",
						support: {
      
							type: "", // 未操作
							support_count: 2,
							unsupport_count: 7
						},
						comment_count: 0,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午18:20",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}
					
				],
				tabIndex: 0,
				scrollInto: ''
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index){
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			}
		}
	}
script>

<style>

style>

显示:

可以看到,已经实现了动态滑动。

2.列表滑动实现

现进一步实现点击选项卡,下面显示对应的列表,使用swiper(滑块视图容器)实现,可以做轮播图和滑动列表,其常见属性和含义如下:

属性名 类型 默认值 含义
indicator-dots Boolean false 是否显示面板指示点
autoplay Boolean false 是否自动切换
current Number 0 当前所在滑块的 index
interval Number 5000 自动切换时间间隔
duration Number 500 滑动动画时长

具体可参考https://uniapp.dcloud.io/component/swiper。

先实现滑块,并与上面的选项卡联动,如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab">
			<swiper-item v-for="(item, index) in tabBars" :key="index">
				<view class="swiper-item">{
    {item.name}}view>
			swiper-item>
		swiper>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index" @follow="follow" @doSupport="doSupport">common-list>
			
			<divider>divider>
		block>
	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support", // 顶
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Brittany",
						userpic: "/static/img/userpic/16.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: false,
						title: "商业数据分析从入门到入职",
						support: {
      
							type: "unsupport", // 踩
							support_count: 2,
							unsupport_count: 3
						},
						comment_count: 5,
						share_count: 1
					},
					{
      
						username: "Jessica",
						userpic: "/static/img/userpic/7.jpg",
						newstime: "2021-01-24 下午14:44",
						isFollow: true,
						title: "Django+Vue开发生鲜电商平台",
						titlepic: "/static/img/datapic/11.jpg",
						support: {
      
							type: "", // 未操作
							support_count: 2,
							unsupport_count: 7
						},
						comment_count: 0,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午18:20",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}
					
				],
				tabIndex: 0,
				scrollInto: ''
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      

		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index){
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
		}
	}
script>

<style>

style>

显示:

可以看到,实现了滑块,并且滑块可以和选项卡实现联动同步

现实现滑块列表示意,如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true" style="height: 100rpx;">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in tabBars" :key="index">
				<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'">
					<view v-for="i in 100" :key="i">{
    {i}}view>
				scroll-view>
			swiper-item>
		swiper>
		<block v-for="(item, index) in list" :key="index">
			
			<common-list :item="item" :index="index" @follow="follow" @doSupport="doSupport">common-list>
			
			<divider>divider>
		block>
	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [{
      
						username: "Corley",
						userpic: "/static/img/userpic/12.jpg",
						newstime: "2021-01-24 上午11:30",
						isFollow: false,
						title: "uni-app入门教程",
						titlepic: "/static/img/datapic/42.jpg",
						support: {
      
							type: "support", // 顶
							support_count: 1,
							unsupport_count: 2
						},
						comment_count: 2,
						share_count: 2
					},
					{
      
						username: "Brittany",
						userpic: "/static/img/userpic/16.jpg",
						newstime: "2021-01-24 下午14:00",
						isFollow: false,
						title: "商业数据分析从入门到入职",
						support: {
      
							type: "unsupport", // 踩
							support_count: 2,
							unsupport_count: 3
						},
						comment_count: 5,
						share_count: 1
					},
					{
      
						username: "Jessica",
						userpic: "/static/img/userpic/7.jpg",
						newstime: "2021-01-24 下午14:44",
						isFollow: true,
						title: "Django+Vue开发生鲜电商平台",
						titlepic: "/static/img/datapic/11.jpg",
						support: {
      
							type: "", // 未操作
							support_count: 2,
							unsupport_count: 7
						},
						comment_count: 0,
						share_count: 2
					},
					{
      
						username: "Ashley",
						userpic: "/static/img/userpic/20.jpg",
						newstime: "2021-01-24 下午18:20",
						isFollow: true,
						title: "uni-app实战之社区交友APP",
						titlepic: "/static/img/datapic/30.jpg",
						support: {
      
							type: "support",
							support_count: 5,
							unsupport_count: 1
						},
						comment_count: 3,
						share_count: 0
					}
				],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}

				],
				tabIndex: 0,
				scrollInto: '',
				// 列表高度
				scrollH: 600
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      
			uni.getSystemInfo({
      
				success:function(res){
      
					console.log(res);
					this.scrollH = res.windowHeight - uni.upx2px(100);
				}
			})
		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index) {
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			}
		}
	}
script>

<style>

style>

为了给swiper设置高度,在生命周期onLoad()通过uni.getSystemInfo()获取windowHeight,即可使用窗口高度,具体为屏幕高度除去NavigationBar和TabBar的高度,再减去选项卡的高度(调用uni.upx2px()转化为以px为单位)。

显示:

可以看到,实现了列表滑动。

3.列表显示和同步

现完善列表项,将之前实现的block放入swiper中,并根据顶部选项卡显示不同的列表。
index.vue如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true" style="height: 100rpx;">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in newsList" :key="index">
				<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'">
					<block v-for="(item2, index2) in item.list" :key="index2">
						
						<common-list :item="item2" :index="index2" @follow="follow" @doSupport="doSupport">common-list>
						
						<divider>divider>
					block>
				scroll-view>
			swiper-item>
		swiper>

	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				newsList: [],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}

				],
				tabIndex: 0,
				scrollInto: '',
				// 列表高度
				scrollH: 600
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      
			uni.getSystemInfo({
      
					success: function(res) {
      
						console.log(res);
						this.scrollH = res.windowHeight - uni.upx2px(100);
					}
				}),
				// 根据选项生成列表
				this.getData();
		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index) {
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					let obj = {
      
						list: [{
      
								username: "Corley",
								userpic: "/static/img/userpic/12.jpg",
								newstime: "2021-01-24 上午11:30",
								isFollow: false,
								title: "uni-app入门教程",
								titlepic: "/static/img/datapic/42.jpg",
								support: {
      
									type: "support", // 顶
									support_count: 1,
									unsupport_count: 2
								},
								comment_count: 2,
								share_count: 2
							},
							{
      
								username: "Brittany",
								userpic: "/static/img/userpic/16.jpg",
								newstime: "2021-01-24 下午14:00",
								isFollow: false,
								title: "商业数据分析从入门到入职",
								support: {
      
									type: "unsupport", // 踩
									support_count: 2,
									unsupport_count: 3
								},
								comment_count: 5,
								share_count: 1
							},
							{
      
								username: "Jessica",
								userpic: "/static/img/userpic/7.jpg",
								newstime: "2021-01-24 下午14:44",
								isFollow: true,
								title: "Django+Vue开发生鲜电商平台",
								titlepic: "/static/img/datapic/11.jpg",
								support: {
      
									type: "", // 未操作
									support_count: 2,
									unsupport_count: 7
								},
								comment_count: 0,
								share_count: 2
							},
							{
      
								username: "Ashley",
								userpic: "/static/img/userpic/20.jpg",
								newstime: "2021-01-24 下午18:20",
								isFollow: true,
								title: "uni-app实战之社区交友APP",
								titlepic: "/static/img/datapic/30.jpg",
								support: {
      
									type: "support",
									support_count: 5,
									unsupport_count: 1
								},
								comment_count: 3,
								share_count: 0
							}
						]
					}
					arr.push(obj)
				}
				this.newsList = arr;
			}
		}
	}
script>

<style>

style>

显示:

可以看到,已经实现了动态切换和显示数据。

4.上拉加载开发

现实现上拉到底部加载数据,需要实现各个选项卡独立上拉加载。

先实现基本的下拉加载,如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true" style="height: 100rpx;">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in newsList" :key="index">
				<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
					
					<block v-for="(item2, index2) in item.list" :key="index2">
						
						<common-list :item="item2" :index="index2" @follow="follow" @doSupport="doSupport">common-list>
						
						<divider>divider>
					block>
					
					<view class="flex align-center justify-center py-3">
						<text class="font text-light-muted">{
    {item.loadmore}}text>
					view>
				scroll-view>
			swiper-item>
		swiper>

	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				newsList: [],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}

				],
				tabIndex: 0,
				scrollInto: '',
				// 列表高度
				scrollH: 600
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      
			uni.getSystemInfo({
      
					success: function(res) {
      
						console.log(res);
						this.scrollH = res.windowHeight - uni.upx2px(100);
					}
				}),
				// 根据选项生成列表
				this.getData();
		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index) {
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多", 
						list: [{
      
								username: "Corley",
								userpic: "/static/img/userpic/12.jpg",
								newstime: "2021-01-24 上午11:30",
								isFollow: false,
								title: "uni-app入门教程",
								titlepic: "/static/img/datapic/42.jpg",
								support: {
      
									type: "support", // 顶
									support_count: 1,
									unsupport_count: 2
								},
								comment_count: 2,
								share_count: 2
							},
							{
      
								username: "Brittany",
								userpic: "/static/img/userpic/16.jpg",
								newstime: "2021-01-24 下午14:00",
								isFollow: false,
								title: "商业数据分析从入门到入职",
								support: {
      
									type: "unsupport", // 踩
									support_count: 2,
									unsupport_count: 3
								},
								comment_count: 5,
								share_count: 1
							},
							{
      
								username: "Jessica",
								userpic: "/static/img/userpic/7.jpg",
								newstime: "2021-01-24 下午14:44",
								isFollow: true,
								title: "Django+Vue开发生鲜电商平台",
								titlepic: "/static/img/datapic/11.jpg",
								support: {
      
									type: "", // 未操作
									support_count: 2,
									unsupport_count: 7
								},
								comment_count: 0,
								share_count: 2
							},
							{
      
								username: "Ashley",
								userpic: "/static/img/userpic/20.jpg",
								newstime: "2021-01-24 下午18:20",
								isFollow: true,
								title: "uni-app实战之社区交友APP",
								titlepic: "/static/img/datapic/30.jpg",
								support: {
      
									type: "support",
									support_count: 5,
									unsupport_count: 1
								},
								comment_count: 3,
								share_count: 0
							}
						]
					}
					arr.push(obj)
				}
				this.newsList = arr;
			},
			// 上拉加载更多
			loadMore(index) {
      
				this.newsList[index].loadmore = '加载中...';
			}
		}
	}
script>

<style>

style>

显示:

可以看到,在下拉到底之前,消息为上拉加载更多,到底后触发@scrolltolower事件,变为加载中...

再实现模拟加载更多数据,如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true" style="height: 100rpx;">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in newsList" :key="index">
				<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
					
					<block v-for="(item2, index2) in item.list" :key="index2">
						
						<common-list :item="item2" :index="index2" @follow="follow" @doSupport="doSupport">common-list>
						
						<divider>divider>
					block>
					
					<view class="flex align-center justify-center py-3">
						<text class="font text-light-muted">{
    {item.loadmore}}text>
					view>
				scroll-view>
			swiper-item>
		swiper>

	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				newsList: [],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}

				],
				tabIndex: 0,
				scrollInto: '',
				// 列表高度
				scrollH: 600
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      
			uni.getSystemInfo({
      
					success: function(res) {
      
						console.log(res);
						this.scrollH = res.windowHeight - uni.upx2px(100);
					}
				}),
				// 根据选项生成列表
				this.getData();
		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index) {
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多", 
						list: [{
      
								username: "Corley",
								userpic: "/static/img/userpic/12.jpg",
								newstime: "2021-01-24 上午11:30",
								isFollow: false,
								title: "uni-app入门教程",
								titlepic: "/static/img/datapic/42.jpg",
								support: {
      
									type: "support", // 顶
									support_count: 1,
									unsupport_count: 2
								},
								comment_count: 2,
								share_count: 2
							},
							{
      
								username: "Brittany",
								userpic: "/static/img/userpic/16.jpg",
								newstime: "2021-01-24 下午14:00",
								isFollow: false,
								title: "商业数据分析从入门到入职",
								support: {
      
									type: "unsupport", // 踩
									support_count: 2,
									unsupport_count: 3
								},
								comment_count: 5,
								share_count: 1
							},
							{
      
								username: "Jessica",
								userpic: "/static/img/userpic/7.jpg",
								newstime: "2021-01-24 下午14:44",
								isFollow: true,
								title: "Django+Vue开发生鲜电商平台",
								titlepic: "/static/img/datapic/11.jpg",
								support: {
      
									type: "", // 未操作
									support_count: 2,
									unsupport_count: 7
								},
								comment_count: 0,
								share_count: 2
							},
							{
      
								username: "Ashley",
								userpic: "/static/img/userpic/20.jpg",
								newstime: "2021-01-24 下午18:20",
								isFollow: true,
								title: "uni-app实战之社区交友APP",
								titlepic: "/static/img/datapic/30.jpg",
								support: {
      
									type: "support",
									support_count: 5,
									unsupport_count: 1
								},
								comment_count: 3,
								share_count: 0
							}
						]
					}
					arr.push(obj)
				}
				this.newsList = arr;
			},
			// 上拉加载更多
			loadMore(index) {
      
				// 获取当前列表
				let item = this.newsList[index];
				// 修改当前列表加载状态
				item.loadmore = '加载中...';
				// 模拟数据请求
				setTimeout(()=>{
      
					// 加载数据
					item.list = [...item.list, ...item.list];
					// 恢复加载状态
					this.newsList[index].loadmore = '上拉加载更多';
				}, 2000)
			}
		}
	}
script>

<style>

style>

其中,[...item.list, ...item.list]是ES6的语法,为扩展运算符,即将item.list再复制一份;
之所以可以通过操作item变量来操作数据,是因为指向了相同的地址,即同一个引用。

显示:

可以看到,模拟出了加载更多数据。

5.封装上拉加载组件

前面实现的上拉加载更多并没有进行判断,可以一直向下滑动加载更多,显然这是不合理的,因此需要进行判断

index.vue如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true" style="height: 100rpx;">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in newsList" :key="index">
				<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
					
					<block v-for="(item2, index2) in item.list" :key="index2">
						
						<common-list :item="item2" :index="index2" @follow="follow" @doSupport="doSupport">common-list>
						
						<divider>divider>
					block>
					
					<view class="flex align-center justify-center py-3">
						<text class="font text-light-muted">{
    {item.loadmore}}text>
					view>
				scroll-view>
			swiper-item>
		swiper>

	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	export default {
      
		data() {
      
			return {
      
				newsList: [],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}

				],
				tabIndex: 0,
				scrollInto: '',
				// 列表高度
				scrollH: 600
			}
		},
		components: {
      
			commonList
		},
		onLoad() {
      
			uni.getSystemInfo({
      
					success: function(res) {
      
						console.log(res);
						this.scrollH = res.windowHeight - uni.upx2px(100);
					}
				}),
				// 根据选项生成列表
				this.getData();
		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index) {
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多", 
						list: [{
      
								username: "Corley",
								userpic: "/static/img/userpic/12.jpg",
								newstime: "2021-01-24 上午11:30",
								isFollow: false,
								title: "uni-app入门教程",
								titlepic: "/static/img/datapic/42.jpg",
								support: {
      
									type: "support", // 顶
									support_count: 1,
									unsupport_count: 2
								},
								comment_count: 2,
								share_count: 2
							},
							{
      
								username: "Brittany",
								userpic: "/static/img/userpic/16.jpg",
								newstime: "2021-01-24 下午14:00",
								isFollow: false,
								title: "商业数据分析从入门到入职",
								support: {
      
									type: "unsupport", // 踩
									support_count: 2,
									unsupport_count: 3
								},
								comment_count: 5,
								share_count: 1
							},
							{
      
								username: "Jessica",
								userpic: "/static/img/userpic/7.jpg",
								newstime: "2021-01-24 下午14:44",
								isFollow: true,
								title: "Django+Vue开发生鲜电商平台",
								titlepic: "/static/img/datapic/11.jpg",
								support: {
      
									type: "", // 未操作
									support_count: 2,
									unsupport_count: 7
								},
								comment_count: 0,
								share_count: 2
							},
							{
      
								username: "Ashley",
								userpic: "/static/img/userpic/20.jpg",
								newstime: "2021-01-24 下午18:20",
								isFollow: true,
								title: "uni-app实战之社区交友APP",
								titlepic: "/static/img/datapic/30.jpg",
								support: {
      
									type: "support",
									support_count: 5,
									unsupport_count: 1
								},
								comment_count: 3,
								share_count: 0
							}
						]
					}
					arr.push(obj)
				}
				this.newsList = arr;
			},
			// 上拉加载更多
			loadMore(index) {
      
				// 获取当前列表
				let item = this.newsList[index];
				// 判断是否处于可加载状态
				if (item.loadmore !== '上拉加载更多') return;
				// 修改当前列表加载状态
				item.loadmore = '加载中...';
				// 模拟数据请求
				setTimeout(()=>{
      
					// 加载数据
					item.list = [...item.list, ...item.list];
					// 恢复加载状态
					this.newsList[index].loadmore = '上拉加载更多';
				}, 2000)
			}
		}
	}
script>

<style>

style>

效果与之前相同,如果loadmore不为上拉加载更多,则会停止加载更多数据。

现将下拉加载更多封装为组件,components/common下新建load-more.vue如下:

<template>
	<view class="flex align-center justify-center py-3">
		<text class="font text-light-muted">{
    {loadmore}}text>
	view>
template>

<script>
	export default {
      
		props: ['loadmore']
	}
script>

<style>
style>

index.vue中使用组件如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true" style="height: 100rpx;">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in newsList" :key="index">
				<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
					
					<block v-for="(item2, index2) in item.list" :key="index2">
						
						<common-list :item="item2" :index="index2" @follow="follow" @doSupport="doSupport">common-list>
						
						<divider>divider>
					block>
					
					<load-more :loadmore="item.loadmore">load-more>
				scroll-view>
			swiper-item>
		swiper>

	view>
template>

<script>
	import commonList from '@/components/common/common-list.vue';
	import loadMore from '@/components/common/load-more.vue';
	export default {
      
		data() {
      
			return {
      
				newsList: [],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}

				],
				tabIndex: 0,
				scrollInto: '',
				// 列表高度
				scrollH: 600
			}
		},
		components: {
      
			commonList,
			loadMore
		},
		onLoad() {
      
			uni.getSystemInfo({
      
					success: function(res) {
      
						console.log(res);
						this.scrollH = res.windowHeight - uni.upx2px(100);
					}
				}),
				// 根据选项生成列表
				this.getData();
		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index) {
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多", 
						list: [{
      
								username: "Corley",
								userpic: "/static/img/userpic/12.jpg",
								newstime: "2021-01-24 上午11:30",
								isFollow: false,
								title: "uni-app入门教程",
								titlepic: "/static/img/datapic/42.jpg",
								support: {
      
									type: "support", // 顶
									support_count: 1,
									unsupport_count: 2
								},
								comment_count: 2,
								share_count: 2
							},
							{
      
								username: "Brittany",
								userpic: "/static/img/userpic/16.jpg",
								newstime: "2021-01-24 下午14:00",
								isFollow: false,
								title: "商业数据分析从入门到入职",
								support: {
      
									type: "unsupport", // 踩
									support_count: 2,
									unsupport_count: 3
								},
								comment_count: 5,
								share_count: 1
							},
							{
      
								username: "Jessica",
								userpic: "/static/img/userpic/7.jpg",
								newstime: "2021-01-24 下午14:44",
								isFollow: true,
								title: "Django+Vue开发生鲜电商平台",
								titlepic: "/static/img/datapic/11.jpg",
								support: {
      
									type: "", // 未操作
									support_count: 2,
									unsupport_count: 7
								},
								comment_count: 0,
								share_count: 2
							},
							{
      
								username: "Ashley",
								userpic: "/static/img/userpic/20.jpg",
								newstime: "2021-01-24 下午18:20",
								isFollow: true,
								title: "uni-app实战之社区交友APP",
								titlepic: "/static/img/datapic/30.jpg",
								support: {
      
									type: "support",
									support_count: 5,
									unsupport_count: 1
								},
								comment_count: 3,
								share_count: 0
							}
						]
					}
					arr.push(obj)
				}
				this.newsList = arr;
			},
			// 上拉加载更多
			loadMore(index) {
      
				// 获取当前列表
				let item = this.newsList[index];
				// 判断是否处于可加载状态
				if (item.loadmore !== '上拉加载更多') return;
				// 修改当前列表加载状态
				item.loadmore = '加载中...';
				// 模拟数据请求
				setTimeout(()=>{
      
					// 加载数据
					item.list = [...item.list, ...item.list];
					// 恢复加载状态
					this.newsList[index].loadmore = '上拉加载更多';
				}, 2000)
			}
		}
	}
script>

<style>

style>

效果与之前相同。

6.封装无数据默认组件

先实现没有数据的情况,如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true" style="height: 100rpx;">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in newsList" :key="index">
				<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
					
					<template v-if="item.list.length > 0">
						
						<block v-for="(item2, index2) in item.list" :key="index2">
							
							<common-list :item="item2" :index="index2" @follow="follow" @doSupport="doSupport">common-list>
							
							<divider>divider>
						block>
						
						<load-more :loadmore="item.loadmore">load-more>
					template>
					
					<template v-else>
						<view class="flex flex-column align-center justify-center pt-7">
							<image src="@/static/common/nothing.jpg" style="width: 300rpx; height: 300rpx;">image>
							<text class="font-md">这里什么都没有哦~text>
						view>
					template>
				scroll-view>
			swiper-item>
		swiper>

	view>
template>

<script>
	const test_data = [{
      
			username: "Corley",
			userpic: "/static/img/userpic/12.jpg",
			newstime: "2021-01-24 上午11:30",
			isFollow: false,
			title: "uni-app入门教程",
			titlepic: "/static/img/datapic/42.jpg",
			support: {
      
				type: "support", // 顶
				support_count: 1,
				unsupport_count: 2
			},
			comment_count: 2,
			share_count: 2
		},
		{
      
			username: "Brittany",
			userpic: "/static/img/userpic/16.jpg",
			newstime: "2021-01-24 下午14:00",
			isFollow: false,
			title: "商业数据分析从入门到入职",
			support: {
      
				type: "unsupport", // 踩
				support_count: 2,
				unsupport_count: 3
			},
			comment_count: 5,
			share_count: 1
		},
		{
      
			username: "Jessica",
			userpic: "/static/img/userpic/7.jpg",
			newstime: "2021-01-24 下午14:44",
			isFollow: true,
			title: "Django+Vue开发生鲜电商平台",
			titlepic: "/static/img/datapic/11.jpg",
			support: {
      
				type: "", // 未操作
				support_count: 2,
				unsupport_count: 7
			},
			comment_count: 0,
			share_count: 2
		},
		{
      
			username: "Ashley",
			userpic: "/static/img/userpic/20.jpg",
			newstime: "2021-01-24 下午18:20",
			isFollow: true,
			title: "uni-app实战之社区交友APP",
			titlepic: "/static/img/datapic/30.jpg",
			support: {
      
				type: "support",
				support_count: 5,
				unsupport_count: 1
			},
			comment_count: 3,
			share_count: 0
		}
	];
	import commonList from '@/components/common/common-list.vue';
	import loadMore from '@/components/common/load-more.vue';
	export default {
      
		data() {
      
			return {
      
				newsList: [],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}

				],
				tabIndex: 0,
				scrollInto: '',
				// 列表高度
				scrollH: 600
			}
		},
		components: {
      
			commonList,
			loadMore
		},
		onLoad() {
      
			uni.getSystemInfo({
      
					success: function(res) {
      
						console.log(res);
						this.scrollH = res.windowHeight - uni.upx2px(100);
					}
				}),
				// 根据选项生成列表
				this.getData();
		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index) {
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多",
						list: []
					}
					if (i % 3 !== 2) {
      
						obj.list = test_data;
					}
					arr.push(obj)
				}
				this.newsList = arr;
			},
			// 上拉加载更多
			loadMore(index) {
      
				// 获取当前列表
				let item = this.newsList[index];
				// 判断是否处于可加载状态
				if (item.loadmore !== '上拉加载更多') return;
				// 修改当前列表加载状态
				item.loadmore = '加载中...';
				// 模拟数据请求
				setTimeout(() => {
      
					// 加载数据
					item.list = [...item.list, ...item.list];
					// 恢复加载状态
					this.newsList[index].loadmore = '上拉加载更多';
				}, 2000)
			}
		}
	}
script>

<style>

style>

base.css定义样式如下:

/* 内外边距 */
.p-2 {
     
	padding: 20rpx;
}

/* flex布局 */
.flex {
     
	display: flex;
}
.flex-column {
     
	flex-direction: column;
}

.align-center {
     
	align-items: center;
}

.justify-between {
     
	justify-content: space-between;
}

.justify-center {
     
	justify-content: center;
}

.flex-1 {
     
	flex: 1;
}

/* 圆角 */
.rounded-circle {
     
	border-radius: 100%;
}

.rounded {
     
	border-radius: 8rpx;
}

/* margin */
.mr-2 {
     
	margin-right: 20rpx;
}

.my-1 {
     
	margin-top: 10rpx;
	margin-bottom: 0rpx;
}

/* padding */
.px-5 {
     
	padding-left: 50rpx;
	padding-right: 50rpx;
}

.px-3 {
     
	padding-left: 30rpx;
	padding-right: 30rpx;
}

.py-3 {
     
	padding-top: 30rpx;
	padding-bottom: 30rpx;
}

.py-2 {
     
	padding-top: 20rpx;
	padding-bottom: 20rpx;
}

.pt-7 {
     
	padding-top: 70rpx;
}

/* 字体 */
.font-lg {
     
	font-size: 40rpx;
}

.font-md {
     
	font-size: 35rpx;
}

.font {
     
	font-size: 30rpx;
}

.font-sm {
     
	font-size: 25rpx;
}

.font-weight-bold {
     
	font-weight: bold;
}

/* 文字颜色 */
.text-white {
     
	color: #FFFFFF;
}

.text-light-muted {
     
	color: #A9A5A0;
}

/* 宽度 */
/* #ifndef APP-PLUS-NVUE */
.w-100 {
     
	width: 100%;
}

/* #endif */

/* scroll-view */
/* #ifndef APP-PLUS-NVUE */
.scroll-row {
     
	width: 100%;
	white-space: nowrap;
}

.scroll-row-item {
     
	display: inline-block !important;
}

/* #endif */

显示:

可以看到,部分页面没有数据,显示的是图片和提示文字。

再实现封装为组件,components/common下新建no-thing.vue如下:

<template>
	<view class="flex flex-column align-center justify-center pt-7">
		<image src="@/static/common/nothing.jpg" style="width: 300rpx; height: 300rpx;">image>
		<text class="font-md">这里什么都没有哦~text>
	view>
template>

<script>
script>

<style>
style>

main.js中引入全局组件如下:

import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

// 引入全局组件
import divider from './components/common/divider.vue';
Vue.component('divider', divider)
import noThing from './components/common/no-thing.vue';
Vue.component('no-thing', noThing)

App.mpType = 'app'

const app = new Vue({
    ...App
})
app.$mount()

index.vue中使用全局组件如下:

<template>
	<view>
		
		<scroll-view scroll-x="true" class="scroll-row" :scroll-into-view="scrollInto" :scroll-with-animation="true" style="height: 100rpx;">
			<view v-for="(item, index) in tabBars" :key="index" class="scroll-row-item px-3 py-2 font-md" :id="'tab'+index"
			 :class="tabIndex === index ? 'text-main font-lg font-weight-bold' : ''" @click="changeTab(index)">{
    {item.name}}view>
		scroll-view>
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in newsList" :key="index">
				<scroll-view scroll-y="true" :style="'height: '+scrollH+'px;'" @scrolltolower="loadMore(index)">
					
					<template v-if="item.list.length > 0">
						
						<block v-for="(item2, index2) in item.list" :key="index2">
							
							<common-list :item="item2" :index="index2" @follow="follow" @doSupport="doSupport">common-list>
							
							<divider>divider>
						block>
						
						<load-more :loadmore="item.loadmore">load-more>
					template>
					
					<template v-else>
						<no-thing>no-thing>
					template>
				scroll-view>
			swiper-item>
		swiper>

	view>
template>

<script>
	const test_data = [{
      
			username: "Corley",
			userpic: "/static/img/userpic/12.jpg",
			newstime: "2021-01-24 上午11:30",
			isFollow: false,
			title: "uni-app入门教程",
			titlepic: "/static/img/datapic/42.jpg",
			support: {
      
				type: "support", // 顶
				support_count: 1,
				unsupport_count: 2
			},
			comment_count: 2,
			share_count: 2
		},
		{
      
			username: "Brittany",
			userpic: "/static/img/userpic/16.jpg",
			newstime: "2021-01-24 下午14:00",
			isFollow: false,
			title: "商业数据分析从入门到入职",
			support: {
      
				type: "unsupport", // 踩
				support_count: 2,
				unsupport_count: 3
			},
			comment_count: 5,
			share_count: 1
		},
		{
      
			username: "Jessica",
			userpic: "/static/img/userpic/7.jpg",
			newstime: "2021-01-24 下午14:44",
			isFollow: true,
			title: "Django+Vue开发生鲜电商平台",
			titlepic: "/static/img/datapic/11.jpg",
			support: {
      
				type: "", // 未操作
				support_count: 2,
				unsupport_count: 7
			},
			comment_count: 0,
			share_count: 2
		},
		{
      
			username: "Ashley",
			userpic: "/static/img/userpic/20.jpg",
			newstime: "2021-01-24 下午18:20",
			isFollow: true,
			title: "uni-app实战之社区交友APP",
			titlepic: "/static/img/datapic/30.jpg",
			support: {
      
				type: "support",
				support_count: 5,
				unsupport_count: 1
			},
			comment_count: 3,
			share_count: 0
		}
	];
	import commonList from '@/components/common/common-list.vue';
	import loadMore from '@/components/common/load-more.vue';
	export default {
      
		data() {
      
			return {
      
				newsList: [],
				// 顶部选项卡
				tabBars: [{
      
						name: '关注'
					},
					{
      
						name: '推荐'
					},
					{
      
						name: '体育'
					},
					{
      
						name: '热点'
					},
					{
      
						name: '财经'
					},
					{
      
						name: '娱乐'
					},
					{
      
						name: '军事'
					},
					{
      
						name: '历史'
					},
					{
      
						name: '本地'
					}

				],
				tabIndex: 0,
				scrollInto: '',
				// 列表高度
				scrollH: 600
			}
		},
		components: {
      
			commonList,
			loadMore
		},
		onLoad() {
      
			uni.getSystemInfo({
      
					success: function(res) {
      
						console.log(res);
						this.scrollH = res.windowHeight - uni.upx2px(100);
					}
				}),
				// 根据选项生成列表
				this.getData();
		},
		methods: {
      
			// 关注
			follow(e) {
      
				console.log('Index followed');
				console.log(e);
				this.list[e].isFollow = true;
				uni.showToast({
      
					title: '关注' + this.list[e].username + '成功'
				})
			},
			// 顶踩操作
			doSupport(e) {
      
				console.log(e);
				// 获取当前列表项
				let item = this.list[e.index];
				let msg = e.type === 'support' ? '顶' : '踩';
				// 之前未顶踩过
				if (item.support.type === '') {
      
					item.support[e.type + '_count']++;
				}
				// 之前已顶过并且现在的操作为踩,则顶-1、踩+1
				else if (item.support.type === 'support' && e.type === 'unsupport') {
      
					item.support.support_count--;
					item.support.unsupport_count++;
				}
				// 之前已踩过并且现在的操作为顶,则踩-1、顶+1
				else if (item.support.type === 'unsupport' && e.type === 'support') {
      
					item.support.unsupport_count--;
					item.support.support_count++;
				}
				item.support.type = e.type;
				uni.showToast({
      
					title: msg + '成功'
				})
			},
			// 切换选项
			changeTab(index) {
      
				if (this.tabIndex === index) {
      
					return;
				}
				this.tabIndex = index;
				// 滚动到指定元素
				this.scrollInto = 'tab' + index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多",
						list: []
					}
					if (i % 3 !== 2) {
      
						obj.list = test_data;
					}
					arr.push(obj)
				}
				this.newsList = arr;
			},
			// 上拉加载更多
			loadMore(index) {
      
				// 获取当前列表
				let item = this.newsList[index];
				// 判断是否处于可加载状态
				if (item.loadmore !== '上拉加载更多') return;
				// 修改当前列表加载状态
				item.loadmore = '加载中...';
				// 模拟数据请求
				setTimeout(() => {
      
					// 加载数据
					item.list = [...item.list, ...item.list];
					// 恢复加载状态
					this.newsList[index].loadmore = '上拉加载更多';
				}, 2000)
			}
		}
	}
script>

<style>

style>

效果与之前相同。

总结

首页的开发标志着进入正式的开发阶段,代码量逐渐增多、逻辑也逐渐复杂,图文列表和滚动选项卡的开发业务逻辑较多,因此也进行了大量的优化,包括组件封装和CSS样式提取等,这都有利于项目的维护和扩展。

你可能感兴趣的:(移动应用开发实战,uni-app开发,社区交友APP,首页开发)