uni-app实战之社区交友APP(7)消息页开发

文章目录

  • 前言
  • 一、消息列表页面开发
    • 1.pages.json配置
    • 2.消息列表组件开发和封装
    • 3.下拉刷新功能实现
    • 4.下拉弹出层组件使用
  • 二、我的好友列表页开发
    • 1.pages.json配置
    • 2.导航组件开发
    • 3.好友列表组件开发
    • 4.性别图标显示
    • 5.封装好友列表组件
  • 三、聊天页面开发
    • 1.pages.json配置
    • 2.聊天输入框组件开发
    • 3.聊天列表组件开发
    • 4.封装聊天列表组件
    • 5.聊天页功能完善
  • 总结

小编目前在做毕业设计,主题为“高考志愿信息交流平台”,面向高中生和大学生,辛苦各位读者大佬朋友们填下问卷,点击链接https://www.wjx.cn/jq/98944127.aspx或扫描二维码、微信小程序码均可,希望各位能提供一些调查数据,先在这里谢过各位了(*^_^*)
问卷1
问卷微信小程序码

前言

本文主要介绍了消息也的开发,主要包括3部分:
消息列表页开发,包括页面配置、消息列表组件的开发和封装、下拉刷新功能和下拉弹出层组件的使用;
我的好友列表页开发,包括页面配置、导航组件开发、好友列表组件开发和封装、以及性别图标显示;
聊天页面开发,包括页面配置、输入框组件开发、聊天列表组件的开发和封装、以及聊天页功能完善。

一、消息列表页面开发

1.pages.json配置

消息页也需要在pages.json中配置顶部导航栏,包括好友和菜单图标,如下:

{
     
    "path" : "pages/msg/msg",
    "style" :                                                                                    
    {
     
        "navigationBarTitleText": "消息列表",
        "enablePullDownRefresh": false,
        "app-plus": {
     
            "titleNView": {
     
                "buttons": [
                    {
     
                        "color":"#333333",
                        "colorPressed":"#FD597C",
                        "float":"left",
                        "fontSize":"20px",
                        "fontSrc":"/static/iconfont.ttf",
                        "text": "\ue60d"
                    },
                    {
     
                        "color":"#333333",
                        "colorPressed":"#FD597C",
                        "float":"right",
                        "fontSize":"20px",
                        "fontSrc":"/static/iconfont.ttf",
                        "text": "\ue652"
                    }
                ]
            }
        }
    }
    
}

为了本文项目练手所需,需要在https://www.iconfont.cn/中下载好友男性女性个人发送等图标,同时将icon.css和iconfont.ttf更新为最新状态。

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

显然,已经配置好左侧和右侧的图标。

2.消息列表组件开发和封装

先实现消息列表项,如下:

<template>
	<view>
		
		<view class="flex align-center p-2">
			<image src="/static/img/userpic/5.jpg" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
			<view class="flex flex-column flex-1">
				<view class="flex align-center justify-between">
					<text class="font-md">Corleytext>
					<text class="font text-secondry">13:34text>
				view>
				<view class="flex align-center justify-between">
					<text class="text-secondry">大佬,你好text>
				view>
			view>
		view>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      
				
			}
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

base.css如下:

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

/* flex布局 */
.flex {
     
	/* #ifndef APP-APP-PLUS-NVUE */
	display: flex;
	/* #endif */
	flex-direction: row;
}

.flex-wrap {
     
	flex-wrap: wrap;
}

.flex-column {
     
	flex-direction: column;
}

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

.align-start {
     
	align-items: flex-start;
}

.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;
}

.mr-1 {
     
	margin-right: 10rpx;
}

.my-2 {
     
	margin-top: 20rpx;
	margin-bottom: 20rpx;
}

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

.mx-2 {
     
	margin-left: 20rpx;
	margin-right: 20rpx;
}

.mx-1 {
     
	margin-left: 10rpx;
	margin-right: 10rpx;
}

.mt-1 {
     
	margin-top: 10rpx;
}

.ml-auto {
     
	margin-left: auto;
}

.ml-2 {
     
	margin-left: 20rpx;
}

/* padding */
.p-2 {
     
	padding-left: 20rpx;
	padding-right: 20rpx;
	padding-top: 20rpx;
	padding-bottom: 20rpx;
}

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

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

.px-2 {
     
	padding-left: 20rpx;
	padding-right: 20rpx;
}

.px-1 {
     
	padding-left: 10rpx;
	padding-right: 10rpx;
}

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

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

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

.pb-2 {
     
	padding-bottom: 20rpx;
}

/* 边框 */
.border {
     
	border-width: 1rpx;
	border-style: solid;
	border-color: #DEE2E6;
}

.border-bottom {
     
	border-bottom: 1rpx solid #DEE2E6;
}

.border-top {
     
	border-top: 1rpx solid #DEE2E6;
}

.border-light-secondary {
     
	border: 1rpx solid #AAA8AB;
}

/* 字体 */
.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;
}

/* 文字换行溢出处理 */
.text-ellipsis {
     
	/* #ifndef APP-PLUS-APP-PLUS-NVUE */
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	/* #endif */
	/* #ifdef APP-PLUS-APP-PLUS-NVUE */
	lines: 1;
	/* #endif */
}

/* 宽度 */
/* #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 */

/* 背景 */
.bg-light {
     
	background-color: #F8F9FA;
}

.bg-secondary {
     
	background-color: #AAA8AB;
}

.bg-white {
     
	background-color: #FFFFFF;
}

.bg-dark {
     
	background-color: #333333;
}

/* 定位 */
.position-relative {
     
	position: relative;
}

.position-absolute {
     
	position: absolute;
}

.position-fixed {
     
	position: fixed;
}

/* 定位-固定顶部 */
.fixed-top {
     
	position: fixed;
	top: 0;
	right: 0;
	left: 0;
	z-index: 1030;
}

/* 定位-固定底部 */
.fixed-bottom {
     
	position: fixed;
	right: 0;
	bottom: 0;
	left: 0;
	z-index: 1030;
}

.top-0 {
     
	top: 0;
}

.left-0 {
     
	left: 0;
}

.right-0 {
     
	right: 0;
}

.bottom-0 {
     
	bottom: 0;
}

common.css如下:

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

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

.text-secondry {
     
	color: #AAA8AB;
}

.text-dark {
     
	color: #333333;
}

/* 下拉弹出框样式 */
.popup-content {
     
	background-color: #fff;
	padding: 7px;
}

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

已经正常显示了消息列表。

再实现新消息提示,需要使用uni-app提供的扩展组件数字角标uni-badge,可参考https://ext.dcloud.net.cn/plugin?id=21。
其位于hello_uniapp演示项目的components/uni-badge目录下,直接将uni-badge目录拷贝至本项目的components/uni-ui目录下即可。

如下:

<template>
	<view>
		
		<view class="flex align-center p-2 border-bottom border-" hover-class="bg-light">
			<image src="/static/img/userpic/5.jpg" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
			<view class="flex flex-column flex-1">
				<view class="flex align-center justify-between">
					<text class="font-md">Corleytext>
					<text class="font text-secondry">13:34text>
				view>
				<view class="flex align-center justify-between">
					<text class="text-secondry">大佬,你好text>
					<uni-badge text="3" type="error">uni-badge>
				view>
			view>
		view>
	view>
template>

<script>
	import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
	export default {
      
		data() {
      
			return {
      
				
			}
		},
		components: {
      
			uniBadge
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

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

可以看到,已经出现消息提示数字角标。

现在处理消息长度过长的问题,使用文字溢出的样式即可,如下:

<view class="flex align-center justify-between">
    <text class="text-secondry text-ellipsis" style="max-width: 500rpx;">大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?text>
    <uni-badge text="3" type="error">uni-badge>
view>

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

多余的文字就会以省略号的形式显示。

将其封装为组件时,需要先构建数据,因为数据中传入的时间形式是时间戳,因此需要转化为时间字符串,使用时间处理库来转换时间,在common目录下新建time.js用于处理时间,如下:

export default {
     
	// 计算当前日期星座
	getHoroscope(date) {
     
		let c = ['摩羯', '水瓶', '双鱼', '白羊', '金牛', '双子', '巨蟹', '狮子', '处女', '天秤', '天蝎', '射手', '摩羯']
		date = new Date(date);
		let month = date.getMonth() + 1;
		let day = date.getDate();
		let startMonth = month - (day - 14 < '865778999988'.charAt(month));
		return c[startMonth] + '座';
	},
	// 计算指定时间与当前的时间差
	sumAge(data) {
     
		let dateBegin = new Date(data.replace(/-/g, "/"));
		let dateEnd = new Date(); //获取当前时间
		let dateDiff = dateEnd.getTime() - dateBegin.getTime(); //时间差的毫秒数
		let dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000)); //计算出相差天数
		let leave1 = dateDiff % (24 * 3600 * 1000) //计算天数后剩余的毫秒数
		let hours = Math.floor(leave1 / (3600 * 1000)) //计算出小时数
		//计算相差分钟数
		let leave2 = leave1 % (3600 * 1000) //计算小时数后剩余的毫秒数
		let minutes = Math.floor(leave2 / (60 * 1000)) //计算相差分钟数
		//计算相差秒数
		let leave3 = leave2 % (60 * 1000) //计算分钟数后剩余的毫秒数
		let seconds = Math.round(leave3 / 1000)
		return dayDiff + "天 " + hours + "小时 ";
	},
	// 获取聊天时间(相差300s内的信息不会显示时间)
	getChatTime(v1, v2) {
     
		v1 = v1.toString().length < 13 ? v1 * 1000 : v1;
		v2 = v2.toString().length < 13 ? v2 * 1000 : v2;
		if (((parseInt(v1) - parseInt(v2)) / 1000) > 300) {
     
			return this.gettime(v1);
		}
	},
	// 人性化时间格式
	gettime(shorttime) {
     
		shorttime = shorttime.toString().length < 13 ? shorttime * 1000 : shorttime;
		let now = (new Date()).getTime();
		let cha = (now - parseInt(shorttime)) / 1000;

		if (cha < 43200) {
     
			// 当天
			return this.dateFormat(new Date(shorttime), "{A} {t}:{ii}");
		} else if (cha < 518400) {
     
			// 隔天 显示日期+时间
			return this.dateFormat(new Date(shorttime), "{Mon}月{DD}日 {A} {t}:{ii}");
		} else {
     
			// 隔年 显示完整日期+时间
			return this.dateFormat(new Date(shorttime), "{Y}-{MM}-{DD} {A} {t}:{ii}");
		}
	},

	parseNumber(num) {
     
		return num < 10 ? "0" + num : num;
	},

	dateFormat(date, formatStr) {
     
		let dateObj = {
     },
			rStr = /\{([^}]+)\}/,
			mons = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];

		dateObj["Y"] = date.getFullYear();
		dateObj["M"] = date.getMonth() + 1;
		dateObj["MM"] = this.parseNumber(dateObj["M"]);
		dateObj["Mon"] = mons[dateObj['M'] - 1];
		dateObj["D"] = date.getDate();
		dateObj["DD"] = this.parseNumber(dateObj["D"]);
		dateObj["h"] = date.getHours();
		dateObj["hh"] = this.parseNumber(dateObj["h"]);
		dateObj["t"] = dateObj["h"] > 12 ? dateObj["h"] - 12 : dateObj["h"];
		dateObj["tt"] = this.parseNumber(dateObj["t"]);
		dateObj["A"] = dateObj["h"] > 12 ? '下午' : '上午';
		dateObj["i"] = date.getMinutes();
		dateObj["ii"] = this.parseNumber(dateObj["i"]);
		dateObj["s"] = date.getSeconds();
		dateObj["ss"] = this.parseNumber(dateObj["s"]);

		while (rStr.test(formatStr)) {
     
			formatStr = formatStr.replace(rStr, dateObj[RegExp.$1]);
		}
		return formatStr;
	},
	// 获取年龄
	getAgeByBirthday(data) {
     
		let birthday = new Date(data.replace(/-/g, "\/"));
		let d = new Date();
		return d.getFullYear() - birthday.getFullYear() - ((d.getMonth() < birthday.getMonth() || d.getMonth() == birthday.getMonth() &&
			d.getDate() < birthday.getDate()) ? 1 : 0);
	}
}

msg.vue创建数据如下:

<template>
	<view>
		
		<block v-for="(item, index) in list" :key="index">
			<view class="flex align-center p-2 border-bottom border-" hover-class="bg-light">
				<image :src="item.avatar" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
				<view class="flex flex-column flex-1">
					<view class="flex align-center justify-between">
						<text class="font-md">{
    {item.username}}text>
						<text class="font-sm text-secondry">{
    {item.update_time | formatTime}}text>
					view>
					<view class="flex align-center justify-between">
						<text class="text-secondry text-ellipsis" style="max-width: 500rpx;">{
    {item.data}}text>
						<uni-badge :text="item.noread" type="error">uni-badge>
					view>
				view>
			view>
		block>
	view>
template>
<script>
	import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
	import $T from '@/common/time.js';
	export default {
      
		data() {
      
			return {
      
				list: [
					{
      
						avatar: '/static/img/userpic/5.jpg',
						username: 'Corley',
						update_time: 1612075613,
						data: '我再看看吧,谢谢大佬',
						noread: 5
					},
					{
      
						avatar: '/static/img/userpic/1.jpg',
						username: 'Brynn',
						update_time: 1612075606,
						data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
						noread: 0
					},
					{
      
						avatar: '/static/img/userpic/3.jpg',
						username: 'Ellie',
						update_time: 1612075255,
						data: '那我也有点懵了',
						noread: 3
					},
					{
      
						avatar: '/static/img/userpic/9.jpg',
						username: 'Ainsley',
						update_time: 1612075983,
						data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
						noread: 1
					},
					{
      
						avatar: '/static/img/userpic/17.jpg',
						username: 'Bella',
						update_time: 1612074571,
						data: 'mysql16进制数据怎么查询才快呢?',
						noread: 2
					}
				]
			}
		},
		components: {
      
			uniBadge
		},
		// 过滤器
		filters: {
      
			formatTime(value) {
      
				return $T.gettime(value);
			}
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

导入了time.js中的gettime()函数获取格式化时间,同时创建过滤器来将时间戳格式化。

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

可以看到,显示出了消息列表,并且时间是格式化后的时间。

此时再封装为组件,components下新建msg目录,下新建msg-list.vue组件如下:

<template>
	<view class="flex align-center p-2 border-bottom border-" hover-class="bg-light">
		<image :src="item.avatar" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
		<view class="flex flex-column flex-1">
			<view class="flex align-center justify-between">
				<text class="font-md">{
    {item.username}}text>
				<text class="font-sm text-secondry">{
    {item.update_time | formatTime}}text>
			view>
			<view class="flex align-center justify-between">
				<text class="text-secondry text-ellipsis" style="max-width: 500rpx;">{
    {item.data}}text>
				<uni-badge :text="item.noread" type="error">uni-badge>
			view>
		view>
	view>
template>

<script>
	import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
	import $T from '@/common/time.js';
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
		// 过滤器
		filters: {
      
			formatTime(value) {
      
				return $T.gettime(value);
			}
		},
		components: {
      
			uniBadge
		}
	}
script>

<style>
style>

msg.vue如下:

<template>
	<view>
		
		<block v-for="(item, index) in list" :key="index">
			<msg-list :item="item" :index="index">msg-list>
		block>
	view>
template>
<script>
	import msgList from '@/components/msg/msg-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [
					{
      
						avatar: '/static/img/userpic/5.jpg',
						username: 'Corley',
						update_time: 1612075613,
						data: '我再看看吧,谢谢大佬',
						noread: 5
					},
					{
      
						avatar: '/static/img/userpic/1.jpg',
						username: 'Brynn',
						update_time: 1612075606,
						data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
						noread: 0
					},
					{
      
						avatar: '/static/img/userpic/3.jpg',
						username: 'Ellie',
						update_time: 1612075255,
						data: '那我也有点懵了',
						noread: 3
					},
					{
      
						avatar: '/static/img/userpic/9.jpg',
						username: 'Ainsley',
						update_time: 1612075983,
						data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
						noread: 1
					},
					{
      
						avatar: '/static/img/userpic/17.jpg',
						username: 'Bella',
						update_time: 1612074571,
						data: 'mysql16进制数据怎么查询才快呢?',
						noread: 2
					}
				]
			}
		},
		components: {
      
			msgList
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

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

3.下拉刷新功能实现

消息页的下拉刷新在pages.json中配置enablePullDownRefresh属性为true,pages.json文件配置如下:

{
     
    "path" : "pages/msg/msg",
    "style" :                                                                                    
    {
     
        "navigationBarTitleText": "消息列表",
        "enablePullDownRefresh": true,
        "app-plus": {
     
            "titleNView": {
     
                "buttons": [
                    {
     
                        "color":"#333333",
                        "colorPressed":"#FD597C",
                        "float":"left",
                        "fontSize":"20px",
                        "fontSrc":"/static/iconfont.ttf",
                        "text": "\ue60d"
                    },
                    {
     
                        "color":"#333333",
                        "colorPressed":"#FD597C",
                        "float":"right",
                        "fontSize":"20px",
                        "fontSrc":"/static/iconfont.ttf",
                        "text": "\ue652"
                    }
                ]
            }
        }
    }
    
}

同时在页面中配置onPullDownRefresh生命周期,如下:

// 监听下拉刷新
onPullDownRefresh() {
     
    this.refresh();
},
methods: {
     
    refresh() {
     
        setTimeout(()=>{
     
            this.list = test_data;
            // 停止下拉刷新
            uni.stopPullDownRefresh();
        }, 2000)
    }
}

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

可以看到,已经实现了下拉刷新的效果。

现在再实现没有数据时的默认界面,通过条件渲染实现,如下:

<template>
	<view>
		<template v-if="list.length > 0">
			
			<block v-for="(item, index) in list" :key="index">
				<msg-list :item="item" :index="index">msg-list>
			block>
		template>
		<template v-else>
			<no-thing>no-thing>
		template>
	view>
template>
<script>
	const test_data = [{
      
			avatar: '/static/img/userpic/5.jpg',
			username: 'Corley',
			update_time: 1612075613,
			data: '我再看看吧,谢谢大佬',
			noread: 5
		},
		{
      
			avatar: '/static/img/userpic/1.jpg',
			username: 'Brynn',
			update_time: 1612075606,
			data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
			noread: 0
		},
		{
      
			avatar: '/static/img/userpic/3.jpg',
			username: 'Ellie',
			update_time: 1612075255,
			data: '那我也有点懵了',
			noread: 3
		},
		{
      
			avatar: '/static/img/userpic/9.jpg',
			username: 'Ainsley',
			update_time: 1612075983,
			data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
			noread: 1
		},
		{
      
			avatar: '/static/img/userpic/17.jpg',
			username: 'Bella',
			update_time: 1612074571,
			data: 'mysql16进制数据怎么查询才快呢?',
			noread: 2
		}
	];
	import msgList from '@/components/msg/msg-list.vue';
	import noThing from '@/components/common/no-thing.vue';
	export default {
      
		data() {
      
			return {
      
				list: []
			}
		},
		components: {
      
			msgList,
			noThing
		},
		// 监听下拉刷新
		onPullDownRefresh() {
      
			this.refresh();
		},
		methods: {
      
			refresh() {
      
				setTimeout(()=>{
      
					this.list = test_data;
					// 停止下拉刷新
					uni.stopPullDownRefresh();
				}, 2000)
			}
		}
	}
script>

<style>

style>

显示:

显然,此时在没有数据时会显示给定的默认组件。

4.下拉弹出层组件使用

下拉弹出框需要使用uni-app提供的扩展组件,直接使用hello_uniapp项目下的components/uni-popup/uni-popup.vue组件即可,将uni-popup目录和同级的uni-transition目录拷贝至本项目的components/uni-ui目录下。

再使用下拉弹出层,如下:

<template>
	<view>
		<template v-if="list.length > 0">
			
			<block v-for="(item, index) in list" :key="index">
				<msg-list :item="item" :index="index">msg-list>
			block>
		template>
		<template v-else>
			<no-thing>no-thing>
		template>
		
		<uni-popup ref="popup" type="top">弹出层uni-popup>
	view>
template>
<script>
	const test_data = [{
      
			avatar: '/static/img/userpic/5.jpg',
			username: 'Corley',
			update_time: 1612075613,
			data: '我再看看吧,谢谢大佬',
			noread: 5
		},
		{
      
			avatar: '/static/img/userpic/1.jpg',
			username: 'Brynn',
			update_time: 1612075606,
			data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
			noread: 0
		},
		{
      
			avatar: '/static/img/userpic/3.jpg',
			username: 'Ellie',
			update_time: 1612075255,
			data: '那我也有点懵了',
			noread: 3
		},
		{
      
			avatar: '/static/img/userpic/9.jpg',
			username: 'Ainsley',
			update_time: 1612075983,
			data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
			noread: 1
		},
		{
      
			avatar: '/static/img/userpic/17.jpg',
			username: 'Bella',
			update_time: 1612074571,
			data: 'mysql16进制数据怎么查询才快呢?',
			noread: 2
		}
	];
	import msgList from '@/components/msg/msg-list.vue';
	import noThing from '@/components/common/no-thing.vue';
	import uniPopup from '@/components/uni-ui/uni-popup/uni-popup.vue';
	export default {
      
		data() {
      
			return {
      
				list: []
			}
		},
		components: {
      
			msgList,
			noThing,
			uniPopup
		},
		// 页面加载
		onLoad() {
      
			this.list = test_data;
		},
		// 监听下拉刷新
		onPullDownRefresh() {
      
			this.refresh();
		},
		// 监听原生导航栏按钮事件
		onNavigationBarButtonTap(e) {
      
			console.log(e);
			switch (e.index){
      
				case 0: // 左边
					break;
				case 1: // 右边
					this.$refs.popup.open();
					break;
				default:
					break;
			}
		},
		methods: {
      
			refresh() {
      
				setTimeout(()=>{
      
					this.list = test_data;
					// 停止下拉刷新
					uni.stopPullDownRefresh();
				}, 2000)
			}
		}
	}
script>

<style>

style>

因为下拉弹出层是通过点击右上角的菜单图标弹出的,因此需要生命周期onNavigationBarButtonTap来实现,其事件参数中有一个值为index,其值表示顶部可点击按钮的下标,从左往右第一个图标为0、第二个图标为1,即点击好友图标时index值为0、点击菜单图标时index值为1。

显示:

可以看到,已经实现了点击弹出下拉弹出框。

现在进一步完善下拉弹出选项,如下:

<template>
	<view>
		<template v-if="list.length > 0">
			
			<block v-for="(item, index) in list" :key="index">
				<msg-list :item="item" :index="index">msg-list>
			block>
		template>
		<template v-else>
			<no-thing>no-thing>
		template>
		
		<uni-popup ref="popup" type="top">
			<view class="popup-content flex align-center justify-center font-md border-bottom" hover-class="bg-light">
				<text class="iconfont icon-sousuo mr-2">text> 添加好友
			view>
			<view class="popup-content flex align-center justify-center font-md" hover-class="bg-light">
				<text class="iconfont icon-shanchu mr-2">text> 清除列表
			view>
		uni-popup>
	view>
template>
<script>
	const test_data = [{
      
			avatar: '/static/img/userpic/5.jpg',
			username: 'Corley',
			update_time: 1612075613,
			data: '我再看看吧,谢谢大佬',
			noread: 5
		},
		{
      
			avatar: '/static/img/userpic/1.jpg',
			username: 'Brynn',
			update_time: 1612075606,
			data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
			noread: 0
		},
		{
      
			avatar: '/static/img/userpic/3.jpg',
			username: 'Ellie',
			update_time: 1612075255,
			data: '那我也有点懵了',
			noread: 3
		},
		{
      
			avatar: '/static/img/userpic/9.jpg',
			username: 'Ainsley',
			update_time: 1612075983,
			data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
			noread: 1
		},
		{
      
			avatar: '/static/img/userpic/17.jpg',
			username: 'Bella',
			update_time: 1612074571,
			data: 'mysql16进制数据怎么查询才快呢?',
			noread: 2
		}
	];
	import msgList from '@/components/msg/msg-list.vue';
	import noThing from '@/components/common/no-thing.vue';
	import uniPopup from '@/components/uni-ui/uni-popup/uni-popup.vue';
	export default {
      
		data() {
      
			return {
      
				list: []
			}
		},
		components: {
      
			msgList,
			noThing,
			uniPopup
		},
		// 页面加载
		onLoad() {
      
			this.list = test_data;
		},
		// 监听下拉刷新
		onPullDownRefresh() {
      
			this.refresh();
		},
		// 监听原生导航栏按钮事件
		onNavigationBarButtonTap(e) {
      
			console.log(e);
			switch (e.index){
      
				case 0: // 左边
					break;
				case 1: // 右边
					this.$refs.popup.open();
					break;
				default:
					break;
			}
		},
		methods: {
      
			refresh() {
      
				setTimeout(()=>{
      
					this.list = test_data;
					// 停止下拉刷新
					uni.stopPullDownRefresh();
				}, 2000)
			}
		}
	}
script>

<style>

style>

显示:

现在给下拉弹出层选项添加点击事件,如下:

<template>
	<view>
		<template v-if="list.length > 0">
			
			<block v-for="(item, index) in list" :key="index">
				<msg-list :item="item" :index="index">msg-list>
			block>
		template>
		<template v-else>
			<no-thing>no-thing>
		template>
		
		<uni-popup ref="popup" type="top">
			<view class="popup-content flex align-center justify-center font-md border-bottom" hover-class="bg-light" @click="popupEvent('friend')">
				<text class="iconfont icon-sousuo mr-2">text> 添加好友
			view>
			<view class="popup-content flex align-center justify-center font-md" hover-class="bg-light" @click="popupEvent('clear')">
				<text class="iconfont icon-shanchu mr-2">text> 清除列表
			view>
		uni-popup>
	view>
template>
<script>
	const test_data = [{
      
			avatar: '/static/img/userpic/5.jpg',
			username: 'Corley',
			update_time: 1612075613,
			data: '我再看看吧,谢谢大佬',
			noread: 5
		},
		{
      
			avatar: '/static/img/userpic/1.jpg',
			username: 'Brynn',
			update_time: 1612075606,
			data: '大佬,你好,我想请教一个关于uni-app的问题,不知道是否方便?',
			noread: 0
		},
		{
      
			avatar: '/static/img/userpic/3.jpg',
			username: 'Ellie',
			update_time: 1612075255,
			data: '那我也有点懵了',
			noread: 3
		},
		{
      
			avatar: '/static/img/userpic/9.jpg',
			username: 'Ainsley',
			update_time: 1612075983,
			data: '真机调试不太方便,我就用浏览器和微信开发者工具调试的。',
			noread: 1
		},
		{
      
			avatar: '/static/img/userpic/17.jpg',
			username: 'Bella',
			update_time: 1612074571,
			data: 'mysql16进制数据怎么查询才快呢?',
			noread: 2
		}
	];
	import msgList from '@/components/msg/msg-list.vue';
	import noThing from '@/components/common/no-thing.vue';
	import uniPopup from '@/components/uni-ui/uni-popup/uni-popup.vue';
	export default {
      
		data() {
      
			return {
      
				list: []
			}
		},
		components: {
      
			msgList,
			noThing,
			uniPopup
		},
		// 页面加载
		onLoad() {
      
			this.list = test_data;
		},
		// 监听下拉刷新
		onPullDownRefresh() {
      
			this.refresh();
		},
		// 监听原生导航栏按钮事件
		onNavigationBarButtonTap(e) {
      
			console.log(e);
			switch (e.index) {
      
				case 0: // 左边
					// 关闭弹出层
					this.$refs.popup.close();
					break;
				case 1: // 右边
					this.$refs.popup.open();
					break;
				default:
					break;
			}
		},
		methods: {
      
			// 下拉刷新
			refresh() {
      
				setTimeout(() => {
      
					this.list = test_data;
					// 停止下拉刷新
					uni.stopPullDownRefresh();
				}, 2000)
			},
			// 弹出层选项点击事件
			popupEvent(e) {
      
				switch (e) {
      
					case 'friend':
						console.log('Adding friend');
						break;
					case 'clear':
						console.log('Clearing list');
						break;
					default:
						break;
				}
				// 关闭弹出层
				this.$refs.popup.close();
			}
		}
	}
script>

<style>

style>

显示:

可以看到,正确地触发了点击事件。

二、我的好友列表页开发

1.pages.json配置

我的好友列表页入口在消息页,如下:

// 监听原生导航栏按钮事件
onNavigationBarButtonTap(e) {
     
    console.log(e);
    switch (e.index) {
     
        case 0: // 左边
        uni.navigateTo({
     
            url: '../user-list/user-list',
        });
            // 关闭弹出层
            this.$refs.popup.close();
            break;
        case 1: // 右边
            this.$refs.popup.open();
            break;
        default:
            break;
    }
},

先创建我的好友列表页user-list,配置pages.json如下:

{
     
    "path" : "pages/user-list/user-list",
    "style" :                                                                                    
    {
     
        "navigationBarTitleText": "",
        "enablePullDownRefresh": false,
        "app-plus": {
     
            // 动画效果
            "animationType":"slide-in-left",
            // 导航栏配置
            "titleNView": {
     
                // 取消返回按钮
                "autoBackButton": false,
                // 搜索框配置
                "searchInput": {
     
                    "align":"center",
                    "backgroundColor":"#F5F4F2",
                    "borderRadius":"4px",
                    "disabled": true,
                    "placeholder": "搜索用户",
                    "placeholderColor": "#6D6C67"
                },
                // 按钮设置
                "buttons": [
                    {
     
                        "color":"#333333",
                        "colorPressed":"#FD597C",
                        "float":"right",
                        "fontSize":"15px",
                        "text": "取消"
                    }
                ]
            }
        }
    }
    
}

其中,animationType用于设置窗口显示的动画效果为新窗体从左侧进入;
autoBackButton用于取消返回按钮。

同时需要实现点击搜索框时跳转到搜索页、点击取消时返回上一页,user-list.vue如下:

<template>
	<view>
		
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      
				
			}
		},
		// 监听点击输入框事件
		onNavigationBarSearchInputClicked() {
      
			uni.navigateTo({
      
				url: '../search/search',
			});
		},
		// 监听点击按钮事件
		onNavigationBarButtonTap() {
      
			uni.navigateBack({
      
				delta: 1
			});
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

显示:

显然,实现了页面切换和动画效果显示。

2.导航组件开发

现进一步完善导航栏,包括互关、关注和粉丝3个导航栏选项,与话题详情页类似。

user-list.vue如下:

<template>
	<view>
		
		<view class="flex align-center py-2">
			<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
			 @click="changeTab(index)">{
    {item.name}} <text v-if="item.num > 0" class="ml-2">{
    {item.num}}text>view>
		view>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      
				tabIndex: 0,
				tabBars: [{
      
						name: '互关',
						num: 2
					},
					{
      
						name: '关注',
						num: 0
					},
					{
      
						name: '粉丝',
						num: 5
					}
				]
			}
		},
		// 监听点击输入框事件
		onNavigationBarSearchInputClicked() {
      
			uni.navigateTo({
      
				url: '../search/search',
			});
		},
		// 监听点击按钮事件
		onNavigationBarButtonTap() {
      
			uni.navigateBack({
      
				delta: 1
			});
		},
		methods: {
      
			changeTab(index) {
      
				this.tabIndex = index;
			}
		}
	}
script>

<style>

style>

其中,好友人数大于0时才显示个数。

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

可以看到,实现了顶部导航栏开发。

3.好友列表组件开发

好友列表和首页列表类似,先构建如下:

<template>
	<view>
		
		<view class="flex align-center py-2">
			<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
			 @click="changeTab(index)">{
    {item.name}} <text v-if="item.num > 0" class="ml-2">{
    {item.num}}text>view>
		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">
							
							<view>{
    {item2}}view>
							
							<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>
	import loadMore from '@/components/common/load-more.vue';
	export default {
      
		data() {
      
			return {
      
				tabIndex: 0,
				tabBars: [{
      
						name: '互关',
						num: 2
					},
					{
      
						name: '关注',
						num: 0
					},
					{
      
						name: '粉丝',
						num: 5
					}
				],
				// 列表高度
				scrollH: 600,
				newsList: []
			}
		},
		// 监听点击输入框事件
		onNavigationBarSearchInputClicked() {
      
			uni.navigateTo({
      
				url: '../search/search',
			});
		},
		// 监听点击按钮事件
		onNavigationBarButtonTap() {
      
			uni.navigateBack({
      
				delta: 1
			});
		},
		onLoad() {
      
			uni.getSystemInfo({
      
				success: function(res) {
      
					console.log(res);
					this.scrollH = res.windowHeight - uni.upx2px(100);
				}
			});
			// 根据选项生成列表
			this.getData();
		},
		methods: {
      
			changeTab(index) {
      
				this.tabIndex = index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 上拉加载更多
			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)
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多",
						list: []
					}
					for (let j = 0; j < this.tabBars[i].num; j++) {
      
						obj.list.push('user' + (j + 1));
					}
					arr.push(obj);
				}
				this.newsList = arr;
			}
		}
	}
script>

<style>

style>

显示:

可以看到,模拟出了好友列表。

进一步构造好友列表项,如下:

<block v-for="(item2, index2) in item.list" :key="index2">
    
    <view class="flex align-center p-2 border-bottom">
        <image src="/static/img/userpic/6.jpg" style="width: 100rpx; height: 100rpx;" class="rounded-circle mr-2">image>
        <view class="flex flex-column flex-1">
            <text class="font-md text-dark">Corleytext>
            <text>text>
        view>
        <view class="uni-icon uni-icon-checkbox-filled text-light-muted">view>
    view>
block>

显示:

可以看到,已经模拟出了好友列表信息。

4.性别图标显示

使用badge组件实现性别图标,为了可以插入性别图标文本,需要对官方提供的uni-badge组件进行稍微改进,如下:

<template>
	<view v-if="text" :class="inverted ? 'uni-badge--' + type + ' uni-badge--' + size + ' uni-badge--' + type + '-inverted' : 'uni-badge--' + type + ' uni-badge--' + size" :style="badgeStyle" class="uni-badge" @click="onClick()"><slot>slot> {
    { text }}view>
template>

user-list.vue如下:


<view class="flex align-center p-2 border-bottom">
    <image src="/static/img/userpic/6.jpg" style="width: 100rpx; height: 100rpx;" class="rounded-circle mr-2">image>
    <view class="flex flex-column flex-1">
        <text class="font-md text-dark">Corleytext>
        <uni-badge text="18" type="error" size="small">
            <text class="iconfont icon-nanxing text-white font-sm" style="margin-right: 5rpx;">text>
        uni-badge>
    view>
    <view class="uni-icon uni-icon-checkbox-filled text-light-muted">view>
view>

components: {
    loadMore,
    uniBadge
},

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

可以看到,已经显示出了性别和年龄。

5.封装好友列表组件

封装好友列表组件之前,需要先构建数据,如下:

<template>
	<view>
		
		<view class="flex align-center py-2">
			<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
			 @click="changeTab(index)">{
    {item.name}} <text v-if="item.num > 0" class="ml-2">{
    {item.num}}text>view>
		view>
		
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in userList" :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">
							
							<view class="flex align-center p-2 border-bottom" hover-class="bg-light">
								<image :src="item2.avatar" style="width: 100rpx; height: 100rpx;" class="rounded-circle mr-2">image>
								<view class="flex flex-column flex-1">
									<text class="font-md text-dark">{
    {item2.username}}text>
									<uni-badge :text="item2.age" :type="item2.sex === 1 ? 'error' : (item2.sex === 2 ? 'primary' : 'default')" size="small">
										<template v-if="item2.sex > 0">
											<text class="iconfont text-white font-sm" :class="item2.sex === 1 ? 'icon-nvxing' : 'icon-nanxing'" style="margin-right: 5rpx;">text>
										template>
									uni-badge>
								view>
								<view class="uni-icon uni-icon-checkbox-filled" :class="item2.isFollow ? 'text-light-muted' : 'text-main'">view>
							view>
						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_data1 = {
      
			avatar: '/static/img/userpic/15.jpg',
			username: 'Corley',
			sex: 1, // 0未知、1女性、2男性
			age: 23,
			isFollow: true
	}
	const test_data2 = {
      
			avatar: '/static/img/userpic/7.jpg',
			username: 'Casey',
			sex: 0, // 0未知、1女性、2男性
			age: 15,
			isFollow: false
	}
	const test_data3 = {
      
			avatar: '/static/img/userpic/13.jpg',
			username: 'Henry',
			sex: 2, // 0未知、1女性、2男性
			age: 18,
			isFollow: true
	}
	import loadMore from '@/components/common/load-more.vue';
	import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
	export default {
      
		data() {
      
			return {
      
				tabIndex: 0,
				tabBars: [{
      
						name: '互关',
						num: 2
					},
					{
      
						name: '关注',
						num: 0
					},
					{
      
						name: '粉丝',
						num: 5
					}
				],
				// 列表高度
				scrollH: 600,
				userList: []
			}
		},
		// 监听点击输入框事件
		onNavigationBarSearchInputClicked() {
      
			uni.navigateTo({
      
				url: '../search/search',
			});
		},
		// 监听点击按钮事件
		onNavigationBarButtonTap() {
      
			uni.navigateBack({
      
				delta: 1
			});
		},
		onLoad() {
      
			uni.getSystemInfo({
      
				success: function(res) {
      
					console.log(res);
					this.scrollH = res.windowHeight - uni.upx2px(100);
				}
			});
			// 根据选项生成列表
			this.getData();
		},
		components: {
      
			loadMore,
			uniBadge
		},
		methods: {
      
			changeTab(index) {
      
				this.tabIndex = index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 上拉加载更多
			loadMore(index) {
      
				// 获取当前列表
				let item = this.userList[index];
				// 判断是否处于可加载状态
				if (item.loadmore !== '上拉加载更多') return;
				// 修改当前列表加载状态
				item.loadmore = '加载中...';
				// 模拟数据请求
				setTimeout(() => {
      
					// 加载数据
					item.list = [...item.list, ...item.list];
					// 恢复加载状态
					this.userList[index].loadmore = '上拉加载更多';
				}, 2000)
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多",
						list: []
					}
					for (let j = 0; j < this.tabBars[i].num; j++) {
      
						if (j % 3 === 0) {
      
							obj.list.push(test_data1);
						}
						else if (j % 3 === 1) {
      
							obj.list.push(test_data2);
						}
						else {
      
							obj.list.push(test_data3);
						}
					}
					arr.push(obj);
				}
				this.userList = arr;
				console.log(this.userList);
			}
		}
	}
script>

<style>

style>

显示:

可以看到,头像、昵称、性别、是否关注、点击效果等均正常显示。

再将其封装为组件,components下新建user-list组件(包含同名目录),如下:

<template>
	<view class="flex align-center p-2 border-bottom" hover-class="bg-light">
		<image :src="item.avatar" style="width: 100rpx; height: 100rpx;" class="rounded-circle mr-2">image>
		<view class="flex flex-column flex-1">
			<text class="font-md text-dark">{
    {item.username}}text>
			<uni-badge :text="item.age" :type="item.sex === 1 ? 'error' : (item.sex === 2 ? 'primary' : 'default')" size="small">
				<template v-if="item.sex > 0">
					<text class="iconfont text-white font-sm" :class="item.sex === 1 ? 'icon-nvxing' : 'icon-nanxing'" style="margin-right: 5rpx;">text>
				template>
			uni-badge>
		view>
		<view class="uni-icon uni-icon-checkbox-filled" :class="item.isFollow ? 'text-light-muted' : 'text-main'">view>
	view>
template>

<script>
	import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
		components: {
      
			uniBadge
		}
	}
script>

<style>
style>

pages/user-list/user-list.vue如下:

<template>
	<view>
		
		<view class="flex align-center py-2">
			<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
			 @click="changeTab(index)">{
    {item.name}} <text v-if="item.num > 0" class="ml-2">{
    {item.num}}text>view>
		view>
		
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in userList" :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">
							
							<user-list :item="item2" :index="index2">user-list>
						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_data1 = {
      
			avatar: '/static/img/userpic/15.jpg',
			username: 'Corley',
			sex: 1, // 0未知、1女性、2男性
			age: 23,
			isFollow: true
	}
	const test_data2 = {
      
			avatar: '/static/img/userpic/7.jpg',
			username: 'Casey',
			sex: 0, // 0未知、1女性、2男性
			age: 15,
			isFollow: false
	}
	const test_data3 = {
      
			avatar: '/static/img/userpic/13.jpg',
			username: 'Henry',
			sex: 2, // 0未知、1女性、2男性
			age: 18,
			isFollow: true
	}
	import loadMore from '@/components/common/load-more.vue';
	import userList from '@/components/user-list/user-list.vue';
	export default {
      
		data() {
      
			return {
      
				tabIndex: 0,
				tabBars: [{
      
						name: '互关',
						num: 2
					},
					{
      
						name: '关注',
						num: 0
					},
					{
      
						name: '粉丝',
						num: 5
					}
				],
				// 列表高度
				scrollH: 600,
				userList: []
			}
		},
		// 监听点击输入框事件
		onNavigationBarSearchInputClicked() {
      
			uni.navigateTo({
      
				url: '../search/search',
			});
		},
		// 监听点击按钮事件
		onNavigationBarButtonTap() {
      
			uni.navigateBack({
      
				delta: 1
			});
		},
		onLoad() {
      
			uni.getSystemInfo({
      
				success: function(res) {
      
					console.log(res);
					this.scrollH = res.windowHeight - uni.upx2px(100);
				}
			});
			// 根据选项生成列表
			this.getData();
		},
		components: {
      
			loadMore,
			userList
		},
		methods: {
      
			changeTab(index) {
      
				this.tabIndex = index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 上拉加载更多
			loadMore(index) {
      
				// 获取当前列表
				let item = this.userList[index];
				// 判断是否处于可加载状态
				if (item.loadmore !== '上拉加载更多') return;
				// 修改当前列表加载状态
				item.loadmore = '加载中...';
				// 模拟数据请求
				setTimeout(() => {
      
					// 加载数据
					item.list = [...item.list, ...item.list];
					// 恢复加载状态
					this.userList[index].loadmore = '上拉加载更多';
				}, 2000)
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多",
						list: []
					}
					for (let j = 0; j < this.tabBars[i].num; j++) {
      
						if (j % 3 === 0) {
      
							obj.list.push(test_data1);
						}
						else if (j % 3 === 1) {
      
							obj.list.push(test_data2);
						}
						else {
      
							obj.list.push(test_data3);
						}
					}
					arr.push(obj);
				}
				this.userList = arr;
				console.log(this.userList);
			}
		}
	}
script>

<style>

style>

效果与之前相同。

现对用户列表页进行进一步优化,页面上下滑动时会出现闪动的情况,这是页面高度设置的问题,指定标签栏样式为style="height: 100rpx;"即可;
同时用户列表的数据较少时,上拉加载不会触发触底事件,此时可以隐藏上拉加载组件。

如下:

<template>
	<view>
		
		<view class="flex align-center" style="height: 100rpx;">
			<view class="flex-1 flex align-center justify-center" v-for="(item ,index) in tabBars" :key="'tab'+index" :class="index === tabIndex ? 'font-lg font-weight-bold text-main' : 'font-md'"
			 @click="changeTab(index)">{
    {item.name}} <text v-if="item.num > 0" class="ml-2">{
    {item.num}}text>view>
		view>
		
		
		<swiper :duration="150" :current="tabIndex" @change="onChangeTab" :style="'height: '+scrollH+'px;'">
			<swiper-item v-for="(item, index) in userList" :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">
							
							<user-list :item="item2" :index="index2">user-list>
						block>
						
						<template v-if="item.list.length > 10">
							<load-more :loadmore="item.loadmore">load-more>
						template>
					template>
					
					<template v-else>
						<no-thing>no-thing>
					template>
				scroll-view>
			swiper-item>
		swiper>
	view>
template>

<script>
	const test_data1 = {
      
			avatar: '/static/img/userpic/15.jpg',
			username: 'Corley',
			sex: 1, // 0未知、1女性、2男性
			age: 23,
			isFollow: true
	}
	const test_data2 = {
      
			avatar: '/static/img/userpic/7.jpg',
			username: 'Casey',
			sex: 0, // 0未知、1女性、2男性
			age: 15,
			isFollow: false
	}
	const test_data3 = {
      
			avatar: '/static/img/userpic/13.jpg',
			username: 'Henry',
			sex: 2, // 0未知、1女性、2男性
			age: 18,
			isFollow: true
	}
	import loadMore from '@/components/common/load-more.vue';
	import userList from '@/components/user-list/user-list.vue';
	export default {
      
		data() {
      
			return {
      
				tabIndex: 0,
				tabBars: [{
      
						name: '互关',
						num: 5
					},
					{
      
						name: '关注',
						num: 3
					},
					{
      
						name: '粉丝',
						num: 12
					}
				],
				// 列表高度
				scrollH: 600,
				userList: []
			}
		},
		// 监听点击输入框事件
		onNavigationBarSearchInputClicked() {
      
			uni.navigateTo({
      
				url: '../search/search',
			});
		},
		// 监听点击按钮事件
		onNavigationBarButtonTap() {
      
			uni.navigateBack({
      
				delta: 1
			});
		},
		onLoad() {
      
			uni.getSystemInfo({
      
				success: function(res) {
      
					console.log(res);
					this.scrollH = res.windowHeight - uni.upx2px(100);
				}
			});
			// 根据选项生成列表
			this.getData();
		},
		components: {
      
			loadMore,
			userList
		},
		methods: {
      
			changeTab(index) {
      
				this.tabIndex = index;
			},
			// 监听滑动
			onChangeTab(e) {
      
				console.log(e);
				this.changeTab(e.detail.current);
			},
			// 上拉加载更多
			loadMore(index) {
      
				// 获取当前列表
				let item = this.userList[index];
				// 判断是否处于可加载状态
				if (item.loadmore !== '上拉加载更多') return;
				// 修改当前列表加载状态
				item.loadmore = '加载中...';
				// 模拟数据请求
				setTimeout(() => {
      
					// 加载数据
					item.list = [...item.list, ...item.list];
					// 恢复加载状态
					this.userList[index].loadmore = '上拉加载更多';
				}, 2000)
			},
			// 获取数据
			getData() {
      
				var arr = [];
				for (let i = 0; i < this.tabBars.length; i++) {
      
					// 生成列表模板
					let obj = {
      
						// 3种状态:1.上拉加载更多;2.加载中...;3.没有更多了。
						loadmore: "上拉加载更多",
						list: []
					}
					for (let j = 0; j < this.tabBars[i].num; j++) {
      
						if (j % 3 === 0) {
      
							obj.list.push(test_data1);
						}
						else if (j % 3 === 1) {
      
							obj.list.push(test_data2);
						}
						else {
      
							obj.list.push(test_data3);
						}
					}
					arr.push(obj);
				}
				this.userList = arr;
				console.log(this.userList);
			}
		}
	}
script>

<style>

style>

显示:

可以看到,有一定的优化效果。

三、聊天页面开发

1.pages.json配置

聊天界面主要实现文字聊天,先创建页面user-chat,其入口为消息列表msg-list,如下:

<template>
	<view class="flex align-center p-2 border-bottom border-" hover-class="bg-light" @click="open()">
		<image :src="item.avatar" style="width: 80rpx; height: 80rpx;" class="rounded-circle mr-2">image>
		<view class="flex flex-column flex-1">
			<view class="flex align-center justify-between">
				<text class="font-md">{
    {item.username}}text>
				<text class="font-sm text-secondry">{
    {item.update_time | formatTime}}text>
			view>
			<view class="flex align-center justify-between">
				<text class="text-secondry text-ellipsis" style="max-width: 500rpx;">{
    {item.data}}text>
				<uni-badge :text="item.noread" type="error">uni-badge>
			view>
		view>
	view>
template>

<script>
	import uniBadge from '@/components/uni-ui/uni-badge/uni-badge.vue';
	import $T from '@/common/time.js';
	export default {
      
		props: {
      
			item: Object,
			index: Number
		},
		// 过滤器
		filters: {
      
			formatTime(value) {
      
				return $T.gettime(value);
			}
		},
		components: {
      
			uniBadge
		},
		methods: {
      
			// 打开聊天页
			open() {
      
				uni.navigateTo({
      
					url: '../../pages/user-chat/user-chat'
				});
			}
		}
	}
script>

<style>
style>

同时配置pages.json如下:

{
     
    "path" : "pages/user-chat/user-chat",
    "style" :                                                                                    
    {
     
        "navigationBarTitleText": "",
        "enablePullDownRefresh": false,
        "app-plus": {
     
            "bounce":"none",
            "titleNView": {
     
                "buttons": [
                    {
     
                        "color":"#333333",
                        "colorPressed":"#FD597C",
                        "float":"right",
                        "fontSize":"20px",
                        "fontSrc":"/static/iconfont.ttf",
                        "text": "\ue60b"
                    }
                ]
            }
        }
    }
    
}

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

可以看到,实现了页面配置。

2.聊天输入框组件开发

现进一步实现底部聊天操作条,user-chat.vue如下:

<template>
	<view>
		
		<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
			<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
			<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
		view>
	view>
template>

显示:

可以看到,已经显示出底部消息发送框,并且有动画效果。

3.聊天列表组件开发

聊天消息列表通过scroll-view组件实现,先实现如下:

<template>
	<view>
		
		<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
			<view v-for="i in 100" :key="i">message{
    {i}}view>
		scroll-view>
		
		<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
			<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
			<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
		view>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      
				scrollH: 500
			}
		},
		onLoad() {
      
			uni.getSystemInfo({
      
				success(res) {
      
					console.log(res);
					this.scrollH = res.windowHeight - uni.upx2px(101);
				}
			})
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

显示:

再实现消息,消息包括头像和消息内容,如下:

<template>
	<view>
		
		<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
			
			<view class="flex align-start px-2 my-2">
				<image src="/static/img/userpic/14.jpg" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
				<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
					大佬,你好
				view>
			view>
			
			<view class="flex align-start px-2" style="flex-direction: row-reverse;">
				<image src="/static/img/userpic/11.jpg" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
				<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
					你好啊,大佬不敢当
				view>
			view>
		scroll-view>
		
		<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
			<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
			<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
		view>
	view>
template>

右侧的消息是通过给左侧的消息添加样式flex-direction: row-reverse;实现的。

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

可以看到,实现了发送消息的列表效果。

4.封装聊天列表组件

封装组件之前需要先构建数据,如下:

<template>
	<view>
		
		<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
			<block v-for="(item, index) in list">
				<view class="flex align-start px-2 my-2" :style="item.user_id === uid ? 'flex-direction: row-reverse;' : ''">
					<image :src="item.avatar" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
					<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
						{
    {item.data}}
					view>
				view>
			block>
		scroll-view>
		
		<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
			<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
			<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
		view>
	view>
template>

<script>
	export default {
      
		data() {
      
			return {
      
				scrollH: 500,
				list: [
					{
      
						user_id: 2,
						username: 'Natalia',
						avatar: '/static/img/userpic/14.jpg',
						data: '大佬,你好',
						type: 'text', // text、image、video、audio、link
						create_time: 1612156732
					},
					{
      
						user_id: 1,
						username: 'Corley',
						avatar: '/static/img/userpic/11.jpg',
						data: '你好啊,大佬不敢当',
						type: 'text', // text、image、video、audio、link
						create_time: 1612156872
					}
				],
				// 模拟当前登录用户userid
				uid: 1
			}
		},
		computed: {
      
			isSelf() {
      
				return this.data 
			}
		},
		onLoad() {
      
			uni.getSystemInfo({
      
				success(res) {
      
					console.log(res);
					this.scrollH = res.windowHeight - uni.upx2px(101);
				}
			})
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

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

再实现封装组件,在components目录下创建user-chat目录,下新建user-chat-list组件,如下:

<template>
	<view class="flex align-start px-2 my-2" :style="isSelf ? 'flex-direction: row-reverse;' : ''">
		<image :src="item.avatar" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
		<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
			{
    {item.data}}
		view>
	view>
template>

<script>
	// 模拟当前登录用户userid
	const uid = 1;
	export default {
      
		props: {
      
			item: Object,
			index: Number,
		},
		computed: {
      
			// 是否是登录用户本人
			isSelf() {
      
				return uid === this.item.user_id;
			}
		},
	}
script>

<style>
style>

user-chat.vue如下:

<template>
	<view>
		
		<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
			<block v-for="(item, index) in list">
				<user-chat-list :item="item" :index="index">user-chat-list>
			block>
		scroll-view>
		
		<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
			<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
			<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
		view>
	view>
template>

<script>
	import userChatList from '@/components/user-chat/user-chat-list.vue';
	export default {
      
		data() {
      
			return {
      
				scrollH: 500,
				list: [
					{
      
						user_id: 2,
						username: 'Natalia',
						avatar: '/static/img/userpic/14.jpg',
						data: '大佬,你好',
						type: 'text', // text、image、video、audio、link
						create_time: 1612156732
					},
					{
      
						user_id: 1,
						username: 'Corley',
						avatar: '/static/img/userpic/11.jpg',
						data: '你好啊,大佬不敢当',
						type: 'text', // text、image、video、audio、link
						create_time: 1612156872
					}
				]
			}
		},
		components: {
      
			userChatList
		},
		onLoad() {
      
			uni.getSystemInfo({
      
				success(res) {
      
					console.log(res);
					this.scrollH = res.windowHeight - uni.upx2px(101);
				}
			})
		},
		methods: {
      
			
		}
	}
script>

<style>

style>

效果与之前相同。

再完善消息时间显示,根据相邻消息对应的时间戳的差来判断是否显示,直接使用time.js中提供的getChatTime(v1, v2)函数来实现即可,默认时间差为5分钟,user-chat-list.vue如下:

<template>
	<view class="">
		
		<template v-if="shortTime">
			<view class="flex align-center justify-center py-2 font-sm text-light-muted">
				{
    {shortTime}}
			view>
		template>
		
		
		<view class="flex align-start px-2 my-2" :style="isSelf ? 'flex-direction: row-reverse;' : ''">
			<image :src="item.avatar" class="rounded-circle" style="width: 100rpx; height: 100rpx;">image>
			<view class="bg-light p-2 rounded mx-2" style="min-width: 100rpx; max-width: 400rpx;">
				{
    {item.data}}
			view>
		view>
	view>
template>

<script>
	// 模拟当前登录用户userid
	const uid = 1;
	import $T from '@/common/time.js';
	export default {
      
		props: {
      
			item: Object,
			index: Number,
			preTime: [Number, String]
		},
		computed: {
      
			// 是否是登录用户本人
			isSelf() {
      
				return uid === this.item.user_id;
			},
			// 转化时间
			shortTime() {
      
				return $T.getChatTime(this.item.create_time, this.preTime);
			}
		},
	}
script>

<style>
style>

需要父组件向该组件传递上一条消息的时间。

父组件user-chat.vue如下:

<template>
	<view>
		
		<scroll-view scroll-y="true" :style="'height:'+scrollH+'px;'">
			<block v-for="(item, index) in list">
				<user-chat-list :item="item" :index="index" :preTime="index > 0 ? list[index-1].create_time : 0">user-chat-list>
			block>
		scroll-view>
		
		<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
			<input type="text" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" />
			<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;">view>
		view>
	view>
template>

显示:

可以看到,已经实现了显示消息时间,并且是人性化的时间显示。

5.聊天页功能完善

首先绑定消息输入框的输入内容;
同时绑定发送按钮的点击事件,实现发送消息;
并且实现在输入消息并发送之后,需要清空输入框;
并绑定@confirm事件,可以在点击完成或前往按钮时触发发送事件;
输入框会部分隐藏消息列表,此时需要修改组件的高度;
在进入聊天页时,需要默认滚到页面最底部,通过scroll-into-view属性来实现。

如下:

<template>
	<view>
		
		<scroll-view scroll-y="true" style="position: absolute; left: 0; top: 0; right: 0; bottom: 100rpx;" :scroll-into-view="scrollInto" scroll-with-animation>
			<block v-for="(item, index) in list">
				<view :id="'chat'+index">
					<user-chat-list :item="item" :index="index" :preTime="index > 0 ? list[index-1].create_time : 0">user-chat-list>
				view>
			block>
		scroll-view>
		
		<view style="height: 100rpx;" class="fixed-bottom flex align-center border-top bg-white">
			<input type="text" v-model="content" value="" class="flex-1 rounded bg-light ml-2" style="padding: 5rpx 0;" placeholder="文明发言" @confirm="submit()" />
			<view class="iconfont icon-fasong flex align-center justify-center font-lg animate__animated" hover-class="animate__jello text-main" style="width: 100rpx;" @click="submit()">view>
		view>
	view>
template>

<script>
	import userChatList from '@/components/user-chat/user-chat-list.vue';
	export default {
      
		data() {
      
			return {
      
				list: [
					{
      
						user_id: 2,
						username: 'Natalia',
						avatar: '/static/img/userpic/14.jpg',
						data: '大佬,你好',
						type: 'text', // text、image、video、audio、link
						create_time: 1612156712
					},
					{
      
						user_id: 2,
						username: 'Corley',
						avatar: '/static/img/userpic/14.jpg',
						data: '我想请教一个关于uni-app的问题,不知道是否方便?',
						type: 'text',
						create_time: 1612156872
					},
					{
      
						user_id: 1,
						username: 'Natalia',
						avatar: '/static/img/userpic/11.jpg',
						data: '你好啊,大佬不敢当',
						type: 'text',
						create_time: 1612156905
					},
					{
      
						user_id: 1,
						username: 'Corley',
						avatar: '/static/img/userpic/11.jpg',
						data: '有什么你就说吧',
						type: 'text',
						create_time: 1612157023
					},
					{
      
						user_id: 1,
						username: 'Corley',
						avatar: '/static/img/userpic/11.jpg',
						data: '只要我会的都会解答',
						type: 'text',
						create_time: 1612157029
					},
					{
      
						user_id: 2,
						username: 'Corley',
						avatar: '/static/img/userpic/14.jpg',
						data: '有几个问题',
						type: 'text',
						create_time: 1612157411
					},
					{
      
						user_id: 2,
						username: 'Natalia',
						avatar: '/static/img/userpic/14.jpg',
						data: '1.在导航栏上单击搜索输入监听搜索框的事件该写在什么位置啊,为什么我写的触发不了?',
						type: 'text',
						create_time: 1612157439
					},
					{
      
						user_id: 2,
						username: 'Corley',
						avatar: '/static/img/userpic/14.jpg',
						data: '2.关注顶踩的动画css怎么获取到的啊?',
						type: 'text',
						create_time: 1612157455
					},
					{
      
						user_id: 2,
						username: 'Natalia',
						avatar: '/static/img/userpic/14.jpg',
						data: '3.首页开发最后代码写完,再点击关注和点赞,踩,就会报错。辛苦看一看啊',
						type: 'text',
						create_time: 1612157503
					},
					{
      
						user_id: 1,
						username: 'Corley',
						avatar: '/static/img/userpic/11.jpg',
						data: '好的,我马上看',
						type: 'text',
						create_time: 1612157821
					}
				],
				content: '',
				scrollInto: ''
			}
		},
		components: {
      
			userChatList
		},
		// 页面加载完成
		onReady() {
      
			this.pageToBottom();
		},
		methods: {
      
			// 发送消息
			submit() {
      
				if (this.content === ''){
      
					return uni.showToast({
      
						title: '消息不能为空',
						icon: 'none'
					});
				}
				let obj = {
      
					user_id: 1,
					username: 'Corley',
					avatar: '/static/img/userpic/11.jpg',
					data: this.content,
					type: 'text',
					create_time: (new Date()).getTime()
				}
				this.list.push(obj);
				// 清空输入框
				this.content = '';
				// 滚动到底部
				this.pageToBottom();
			},
			// 滚动到底部
			pageToBottom() {
      
				let lastIndex = this.list.length - 1;
				if (lastIndex < 0) return;
				this.scrollInto = 'chat' + lastIndex;
			}
		}
	}
script>

<style>

style>

显示:

可以看到,此时表现更好。

现在还需要完善聊天页导航栏标题,应该是当前聊天的用户名,使用页面消息传递的方式实现,msg-list.vue如下:

open() {
     
    uni.navigateTo({
     
        url: '../../pages/user-chat/user-chat?username=' + this.item.username
    });
}

user-chat.vue如下:

onLoad(e) {
     
    console.log(e);
    this.username = e.username;
},
// 页面加载完成
onReady() {
     
    this.pageToBottom();
    uni.setNavigationBarTitle({
     
        title: this.username
    });
},

显示:

此时,顶部导航栏标题是用户名。

总结

使用已经实现好的库和组件可以加速开发,文中使用到了uni-app官方提供的扩展组件uni-badge用于显示消息数、uni-popup用于实现下拉弹出框,同时使用专门的JS库来进行时间处理,不需要自己再造轮子,大大加快了开发。

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