【UniApp开发小程序】小程序私聊页面完善(仿微信带尾巴聊天气泡组件封装、滑至顶端获取历史聊天数据逻辑优化)【后端基于若依管理系统开发】

文章目录

  • 说明
  • 仿微信带尾巴聊天气泡组件
    • 效果展示
    • 组件整体代码
      • 气泡主体
      • 气泡尾巴
    • 使用
  • 私聊页面滑动到顶部获取历史数据
  • 页面整体代码

说明

之前已经在【UniApp开发小程序】私聊功能uniapp界面实现 (买家、卖家 沟通商品信息)【后端基于若依管理系统开发】这篇文章中介绍了私聊页面的实现,这篇文章主要针对一些细节进行完善

仿微信带尾巴聊天气泡组件

效果展示

在这里插入图片描述

组件整体代码

<template>
	<view class="bubble" :class="tailDirection" :style="{'--tail-color':backgroundColor}">
		<text class="content" :style="{'background-color': backgroundColor,'color':fontColor}">{{text}}</text>
	</view>
</template>

<script>
	export default {
		props: {
			// 气泡的尾巴朝向 left:左 right:右
			tailDirection: {
				type: String,
				default: 'left'
			},
			// 气泡的背景颜色
			backgroundColor: {
				type: String,
				default: '#ffffff'
			},
			// 气泡的字体颜色
			fontColor: {
				type: String,
				default: '#000000'
			},
			// 气泡里面显示的文字
			text: {
				type: String,
				default: ''
			}
		},
		data: {
			contentId: 0,
			contentStyle: {}
		},
	}
</script>

<style lang="scss">
	.bubble {
		display: inline-flex;
		position: relative;
		align-items: center;

		.content {
			// 设置气泡的内间距,让气泡边缘距离文字有一定的距离
			padding: 10px 10px;
			// 设置气泡的边框半径,使边框有弧度
			border-radius: 8px;
			font-family: sans-serif;
			// 解决英文字符串、数字不换行的问题
			word-break: break-all;
			word-wrap: break-word;
		}
	}

	.left {
		margin-left: 5px;
	}

	.right {
		margin-right: 5px;
	}

	.left:before {
		position: absolute;
		content: "\00a0";
		width: 0px;
		height: 0px;
		border-width: 5px 10px 5px 0;
		border-style: solid;
		border-color: transparent var(--tail-color) transparent transparent;
		top: 10px;
		left: -10px;
	}

	.right:before {
		position: absolute;
		content: "\00a0";
		// display: inline-block;
		width: 0px;
		height: 0px;
		border-width: 5px 0px 5px 10px;
		border-style: solid;
		border-color: transparent transparent transparent var(--tail-color);
		top: 10px;
		right: -10px;
	}
</style>

气泡主体

气泡主体主要使用一个text标签来存储文字内容,并设置背景颜色、边框半径、内间距、单词和数字分解

气泡尾巴

【伪元素(气泡尾巴)的css介绍】
.left:before.right:before 两个伪元素主要用来给气泡添加尾巴,一个向左、一个向右

  • :before 使用该伪元素可以用来向被选元素的内容前插入一个虚拟元素,用于显示一些额外的内容或进行样式修饰,比如添加图标、箭头、编号……
  • position: absolute; 将伪元素的位置设置为绝对定位,以便于相对于其父元素位置进行位置设置
  • content: "\00a0"; 添加一个不间断空格,作为伪元素的填充内容
  • width: 0px; height: 0px; 将元素的宽度和高度设置为0
  • border-width: 5px 10px 5px 0; 设置边框宽度,按顺序分别为上边框、右边框、下边框和左边框,其中左边框为0,因此左边不需要边框
  • border-style: solid; 将边框样式设置为实线
  • border-color: transparent var(--tail-color) transparent transparent; 设置边框颜色
  • top: 10px; left: -10px; 设置伪元素相对于父元素的位置

【修改一】
先将view的宽高都设为0,然后给view设置较粗的边框,最终渲染的时候,边框与边框会相交出三角形。当每条边框都设置不同的颜色时,效果如下图所示

.left:before {
	position: absolute;
	content: "\00a0";
	width: 0px;
	height: 0px;
	border-width: 10px 10px 10px 10px;
	border-style: solid;
	border-color: black var(--tail-color) blue yellow; 
	top: 10px;
	left: -30px;
}

在这里插入图片描述

【修改二】
要想只保留最右边的三角形,只需要将其他3个三角形都设置为透明即可

.left:before {
	position: absolute;
	content: "\00a0";
	width: 0px;
	height: 0px;
	border-width: 10px 10px 10px 10px;
	border-style: solid;
	border-color: transparent var(--tail-color) transparent transparent;
	top: 10px;
	left: -30px;
}

在这里插入图片描述
【修改三】
因为该三角形只由上边框、右边框、左边框相交即可得到,因此可以将左边框的宽度设置为0。border-width: 10px 10px 10px 0;分别设置上、右、下、左边框

.left:before {
	position: absolute;
	content: "\00a0";
	width: 0px;
	height: 0px;
	border-width: 10px 10px 10px 0;
	border-style: solid;
	border-color: transparent var(--tail-color) transparent transparent;
	top: 10px;
	left: -30px;
}

在这里插入图片描述
【修改四】
下面需要修改一下伪元素相对于父元素的位置,因为右边框的宽度是10px,通过left: -10px;让伪元素向左边偏移10px,这样尾巴刚好贴紧气泡

.left:before {
	position: absolute;
	content: "\00a0";
	width: 0px;
	height: 0px;
	border-width: 10px 10px 10px 0;
	border-style: solid;
	border-color: transparent var(--tail-color) transparent transparent;
	top: 10px;
	left: -10px;
}

在这里插入图片描述
【最终版】
最好修改一下上下边框的宽度,让尾巴瘦一点

.left:before {
	position: absolute;
	content: "\00a0";
	width: 0px;
	height: 0px;
	border-width: 5px 10px 5px 0;
	border-style: solid;
	border-color: transparent var(--tail-color) transparent transparent;
	top: 10px;
	left: -10px;
}

在这里插入图片描述

【尾巴颜色控制】
需要注意的是,尾巴的颜色也需要可以由开发者去定义,因此使用 var(--tail-color) 来控制伪元素从变量中获取颜色,并在下面的代码中对颜色进行赋值

<view class="bubble" :class="tailDirection" :style="{'--tail-color':backgroundColor}">

使用

如下面的代码所示,开发者可以在使用组件的时候设置气泡的尾巴朝向、背景颜色、字体颜色和气泡文字

props: {
	// 气泡的尾巴朝向 left:左 right:右
	tailDirection: {
		type: String,
		default: 'left'
	},
	// 气泡的背景颜色
	backgroundColor: {
		type: String,
		default: '#ffffff'
	},
	// 气泡的字体颜色
	fontColor: {
		type: String,
		default: '#000000'
	},
	// 气泡里面显示的文字
	text: {
		type: String,
		default: ''
	}
},

【引入组件并使用的代码】

<template>
  <view class="page">
    <bubble tailDirection="right" color="blue" text="Hello, I'm chat bubble!"  backgroundColor="#00ffff" fontColor="#ff0000"/>
  </view>
</template>

<script>
import Bubble from '@/components/bubble/bubble.vue'

export default {
  components: {
    Bubble
  }
}
</script>

<style>
.page {
  padding: 20px;
}
</style>

【效果】
在这里插入图片描述

私聊页面滑动到顶部获取历史数据

相较于上篇文章,除了替换了聊天气泡,聊天页面在加载历史数据的时候添加了“正在加载”字样,如下图所示

在这里插入图片描述
当获取历史消息时,将loadHistory设置为true,显示“正在加载”,同时让用户在等待此次加载结束之后才能重新加载下一批历史消息

<!-- 显示加载相关字样 -->
<u-loadmore v-if="loadHistory==true" status="loading" />
/**
* 滑到最顶端,分页加一,拉取更早的数据
 */
getHistoryChat() {
	// console.log("获取历史消息")
	if (this.messageList.length < this.total && this.loadHistory == false) {
		// 当目前的消息条数小于消息总量的时候,才去查历史消息
		this.page.pageNum++;
		this.loadHistory = true;
		this.scrollToView = '';
		this.listChat().then(() => {
			setTimeout(() => {
				this.loadHistory = false;
			}, 1000)
		})
	}
},

页面整体代码

【私聊页面】

<template>
	<view style="height:100vh;">
		<!-- @scrolltoupper:上滑到顶部执行事件,此处用来加载历史消息 -->
		<!-- scroll-with-animation="true" 设置滚动条位置的时候使用动画过渡,让动作更加自然 -->
		<scroll-view :scroll-into-view="scrollToView" scroll-y="true" class="messageListScrollView"
			:style="{height:scrollViewHeight}" @scrolltoupper="getHistoryChat()"
			:scroll-with-animation="!isFirstListChat" ref="chatScrollView">
			<!-- 显示加载相关字样 -->
			<u-loadmore v-if="loadHistory==true" status="loading" />
			<view v-for="(message,index) in messageList" :key="message.id" :id="`message`+message.id"
				style="width: 750rpx;min-height: 60px;">
				<view style="height: 10px;"></view>
				<view v-if="message.type==0" class="messageItemLeft">
					<view style="width: 8px;"></view>
					<u--image :showLoading="true" :src="you.avatar" width="50px" height="50px" radius="3"></u--image>
					<view style="width: 7px;"></view>
					<view class="messageBubble">
						<bubble tailDirection="left" :text="message.content" backgroundColor="#ffffff" />
					</view>
				</view>
				<view v-if="message.type==1" class="messageItemRight">
					<view class="messageBubble">
						<bubble tailDirection="right" :text="message.content" backgroundColor="#95EC69" />
					</view>
					<view style="width: 7px;"></view>
					<u--image :showLoading="true" :src="me.avatar" width="50px" height="50px" radius="3"></u--image>
					<view style="width: 8px;"></view>
				</view>
			</view>
		</scroll-view>

		<view class="messageSend">
			<view class="messageInput">
				<u--textarea v-model="messageInput" placeholder="请输入消息内容" autoHeight>
				</u--textarea>
			</view>
			<view style="width:5px"></view>
			<view class="commmitButton" @click="send()">发 送</view>
		</view>
	</view>

</template>

<script>
	import {
		getUserProfileVo
	} from "@/api/user";
	import {
		listChat
	} from "@/api/market/chat.js";
	import Bubble from '@/components/bubble/bubble.vue'

	let socket;
	export default {
		components: {
			Bubble
		},
		data() {
			return {
				webSocketUrl: "",
				socket: null,
				messageInput: '',
				// 我自己的信息
				me: {},
				// 对方信息
				you: {},

				scrollViewHeight: undefined,
				messageList: [],
				// 底部滑动到哪里
				scrollToView: '',
				page: {
					pageNum: 1,
					pageSize: 20
				},
				isFirstListChat: true,
				// 是否正在加载更多历史数据
				loadHistory: false,
				// 消息总条数
				total: 0,
				// 数据加载状态
				loadmoreStatus: "loadmore",
			}
		},
		created() {
			this.me = uni.getStorageSync("curUser");
		},
		beforeDestroy() {
			console.log("执行销毁方法");
			this.endChat();
		},
		onLoad(e) {
			// 设置初始高度
			this.scrollViewHeight = `calc(100vh - 20px - 44px)`;
			this.you = JSON.parse(decodeURIComponent(e.you));
			uni.setNavigationBarTitle({
				title: this.you.nickname,
			})
			this.startChat();
			this.listChat();
			this.receiveMessage();
		},

		onReady() {
			// 监听键盘高度变化,以便设置输入框的高度
			uni.onKeyboardHeightChange(res => {
				let keyBoardHeight = res.height;
				console.log("keyBoardHeight:" + keyBoardHeight);
				this.scrollViewHeight = `calc(100vh - 20px - 44px - ${keyBoardHeight}px)`;

				this.scrollToView = '';
				setTimeout(() => {
					this.scrollToView = 'message' + this.messageList[this
						.messageList.length - 1].id;
				}, 150)
			})
		},
		methods: {

			/**
			 * 发送消息
			 */
			send() {
				if (this.messageInput != '') {
					let message = {
						from: this.me.userName,
						to: this.you.username,
						text: this.messageInput
					}
					// console.log("this.socket.send:" + this.$socket)
					// 将组装好的json发送给服务端,由服务端进行转发
					this.$socket.send({
						data: JSON.stringify(message)
					});
					this.total++;
					let newMessage = {
						// code: this.messageList.length,
						type: 1,
						content: this.messageInput
					};
					this.messageList.push(newMessage);
					this.messageInput = '';
					this.toBottom();
				}
			},
			/**
			 * 开始聊天
			 */
			startChat() {
				let message = {
					from: this.me.userName,
					to: this.you.username,
					text: "",
					status: "start"
				}
				// 告诉服务端要开始聊天了
				this.$socket.send({
					data: JSON.stringify(message)
				});
			},
			/**
			 * 结束聊天
			 */
			endChat() {
				let message = {
					from: this.me.userName,
					to: this.you.username,
					text: "",
					status: "end"
				}
				// 告诉服务端要结束聊天了
				this.$socket.send({
					data: JSON.stringify(message)
				});
			},
			/**
			 * 接收消息
			 */
			receiveMessage() {
				this.$socket.onMessage((response) => {
					// console.log("接收消息:" + response.data);
					let message = JSON.parse(response.data);

					let newMessage = {
						// code: this.messageList.length,
						type: 0,
						content: message.text
					};
					this.messageList.push(newMessage);
					this.total++;
					// 让scroll-view自动滚动到最新的数据那里
					// this.$nextTick(() => {
					// 	// 滑动到聊天区域最底部
					// 	this.scrollToView = 'message' + this.messageList[this
					// 		.messageList.length - 1].id;
					// });
					this.toBottom();
				})
			},
			/**
			 * 查询对方和自己最近的聊天数据
			 */
			listChat() {
				return new Promise((resolve, reject) => {
					listChat(this.you.username, this.page).then(res => {
						for (var i = 0; i < res.rows.length; i++) {
							this.total = res.total;
							if (res.rows[i].fromWho == this.me.userName) {
								res.rows[i].type = 1;
							} else {
								res.rows[i].type = 0;
							}
							// 将消息放到数组的首位
							this.messageList.unshift(res.rows[i]);
						}

						if (this.isFirstListChat == true) {
							// this.$nextTick(function() {
							// 	// 滑动到聊天区域最底部
							// 	this.scrollToView = 'message' + this.messageList[this
							// 		.messageList.length - 1].id;
							// })
							this.isFirstListChat = false;
							this.toBottom();
						}
						resolve();
					})

				})

			},
			/**
			 * 滑到最顶端,分页加一,拉取更早的数据
			 */
			getHistoryChat() {
				// console.log("获取历史消息")
				if (this.messageList.length < this.total && this.loadHistory == false) {
					// 当目前的消息条数小于消息总量的时候,才去查历史消息
					this.page.pageNum++;
					this.loadHistory = true;
					this.scrollToView = '';
					this.listChat().then(() => {
						setTimeout(() => {
							this.loadHistory = false;
						}, 1000)
					})
				}
			},
			/**
			 * 滑动到聊天区域最底部
			 */
			toBottom() {
				// 让scroll-view自动滚动到最新的数据那里
				this.scrollToView = '';
				setTimeout(() => {
					// 滑动到聊天区域最底部
					this.scrollToView = 'message' + this.messageList[this
						.messageList.length - 1].id;
				}, 150)
			}
		}
	}
</script>

<style lang="scss">
	.messageListScrollView {
		background: #F5F5F5;
		// overflow: auto;

		.messageBubble {
			max-width: calc(750rpx - 10px - 50px - 15px - 10px - 50px - 15px);
			padding: 0px 0px 10px 0px;
		}

		.messageItemLeft {
			display: flex;
			align-items: flex-start;
			justify-content: flex-start;
		}

		.messageItemRight {
			display: flex;
			align-items: flex-start;
			justify-content: flex-end;
		}
	}

	.messageSend {
		display: flex;

		background: #ffffff;
		padding-top: 5px;
		padding-bottom: 15px;

		.messageInput {
			border: 1px #EBEDF0 solid;
			border-radius: 5px;
			width: calc(750rpx - 65px);
			margin-left: 5px;
		}

		.commmitButton {
			height: 38px;
			border-radius: 5px;
			width: 50px;
			display: flex;
			align-items: center;
			justify-content: center;
			color: #ffffff;
			background: #3C9CFF;

		}
	}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/98127.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

跨屏无界 | ZlongGames 携手 Google Play Games 打造无缝游戏体验

一款经典游戏&#xff0c;会在时间的沉淀中被每一代玩家所怀念&#xff0c;经久不衰。对于紫龙游戏来讲&#xff0c;他们就是这样一群怀揣着创作出经典游戏的初心而聚集在一起的团队&#xff0c;致力于研发出被广大玩家喜爱的作品。 从 2015 年团队成立&#xff0c;到 2019 年走…

Shell - 加固系统配置

文章目录 #! /bin/bash # Function:对账户的密码的一些加固 read -p "设置密码最多可多少天不修改&#xff1a;" A read -p "设置密码修改之间最小的天数&#xff1a;" B read -p "设置密码最短的长度&#xff1a;" C read -p "设置密码失效…

matlab 点云的二进制形状描述子

目录 一、功能概述1、算法概述2、主要函数3、参考文献二、代码示例三、结果展示四、参数解析输入参数名称-值对应参数输出参数五、参考链接本文由CSDN点云侠原创,

【python爬虫】—图片爬取

图片爬取 需求分析Python实现 需求分析 从https://pic.netbian.com/4kfengjing/网站爬取图片&#xff0c;并保存 Python实现 获取待爬取网页 def get_htmls(pageslist(range(2, 5))):"""获取待爬取网页"""pages_list []for page in pages:u…

数据库——Redis 没有使用多线程?为什么不使用多线程?

文章目录 Redis6.0 之后为何引入了多线程&#xff1f; 虽然说 Redis 是单线程模型&#xff0c;但是&#xff0c; 实际上&#xff0c;Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。 不过&#xff0c;Redis 4.0 增加的多线程主要是针对一些大键值对的删除操作的命令&a…

怎么用postman连接websocket

点击右侧栏的Collections&#xff0c;然后点击旁边的New&#xff0c;然后点击其中的WebSocket Request,然后输入Url&#xff0c;点击Connection&#xff0c;这里需要注意的是Url不能加上http://&#xff0c;因为这个不是http协议。

alibabacloud的简单使用,nacos配置中心+服务中心。作者直接给自己写的源码

文章目录 依赖关键主要的程序启动文件配置文件bootstrap.yml依赖文件nacos配置中心上的文件截图 启动成功截图参考文档 依赖关键 SpringBoot版本和com.alibaba.cloud版本需要对应&#xff0c;不然会程序会启动失败作者使用的版本 SpringBoot: 2.1.6.RELEASE alibabacloud: 2.…

YOLOv7-tracker 目标追踪 输入视频帧

目录 1 项目安装1.1 环境搭建1.2 项目下载1.3 权重下载1.4 环境安装1.5 上传待检测的视频帧 2 视频帧检测与追踪2.1 检测与追踪2.3 结果 参考项目&#xff1a;https://github.com/JackWoo0831/Yolov7-tracker/tree/master github链接&#xff1a;https://github.com/Whiffe/Yo…

自然语言处理(NLP)是什么?

NLP(自然语言处理) 和 Phoebe Liu 的简介 您有没有和聊天机器人互动过&#xff1f;或者您是否向虚拟助手&#xff0c;例如 Siri、Alexa 或您车上的车载娱乐系统发出过某些请求&#xff1f;您使用过在线翻译吗&#xff1f;我们大多数人都曾与这些人工智能 (AI) 互动过&#xff…

vue naive ui 按钮绑定按键

使用vue (naive ui) 绑定Enter 按键 知识点: 按键绑定Button全局挂载使得message,notification, dialog, loadingBar 等NaiveUI 生效UMD方式使用vue 与 naive ui将vue默认的 分隔符大括号 替换 为 [[ ]] <!DOCTYPE html> <html lang"en"> <head>…

C语言指针进阶(3)

这节我们来总结一下指针和数组面试题。 在这节我们主要用到这样几个知识点&#xff1a; 1.数组名是数组首元素的地址。 但是有两个例外&#xff1a; 2.sizeof(数组名)&#xff0c;这里的数组名表示整个数组&#xff0c;计算的是整个数组的大小&#xff0c;单位是字节。 3.&…

如何在Windows本地快速搭建SFTP文件服务器,并通过端口映射实现公网远程访问

文章目录 1. 搭建SFTP服务器1.1 下载 freesshd服务器软件1.3 启动SFTP服务1.4 添加用户1.5 保存所有配置 2 安装SFTP客户端FileZilla测试2.1 配置一个本地SFTP站点2.2 内网连接测试成功 3 使用cpolar内网穿透3.1 创建SFTP隧道3.2 查看在线隧道列表 4. 使用SFTP客户端&#xff0…

四、MySQL(表操作)如何添加字段?修改表?删除字段?修改表名?删除表?格式化某张表?

1、添加字段 &#xff08;1&#xff09;基础语法&#xff1a; alter table 表名 add 字段名 类型名(长度) [comment注释] [约束]; &#xff08;2&#xff09;示例&#xff1a;添加nickname这个字段 2、修改表 修改表中某个字段的【数据类型】/【数据类型&字段名】 &…

c# - - - 安装.net core sdk

如图&#xff0c;安装的是.Net Core 2.2版本 查看安装成功

【Centos8_配置单节点伪分布式Spark环境】

安装centos8 jdk部署伪分布式spark环境 安装Centos8 环境下的JDK 下载jdk linux版本 下载链接&#xff1a; jdk-8u381-linux-x64.tar.gz 将该文件上传到Centos8 主机 部署配置jdk&#xff08;java8&#xff09; # 解压到指定路径 [lhangtigerkeen Downloads]$ sudo tar …

vite 配置自动补全文件的后缀名

vite 不建议自动补全&#xff0c;文件的后缀名的 const Home ()>import("/views/Home.vue");文件是必须要加上 .vue 的后缀名的 如果 想要像 webpack 一样的不用写&#xff0c; 可以在vite.config.js中配置如下就可以了

UDP通信、本地套接字

#include <sys/types.h> #include <sys/socket > ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);- 参数&#xff1a;- sockfd : 通信的fd- buf : 要发送的数据- len : 发送数据的长度…

ElasticSearch-集成ik分词器

本文已收录于专栏 《中间件合集》 目录 背景介绍版本选择优势说明集成过程1.下载安装包2.解压安装包3.重启ElasticSearch服务3.1通过ps -ef | grep elastic查看正在启动的es进程号3.2使用kill -9 xxx 杀死进程3.3使用 ./elasticsearch 启动es服务 分词测试细粒度分词方式分词请…

删除命名空间一直处于Terminating

删除命名空间一直处于Terminating 通常删除命名空间或者其他资源一直处于Terminating状态&#xff0c;是由于资源调度到的节点处于NotReady状态&#xff0c;需要将节点重新加入到集群使其状态变为Ready状态才能解决问题&#xff0c;当node重新加入处于Ready状态后&#xff0c;…

Linux centos7 bash编程(break和continue)

在学习shell知识时&#xff0c;简单编程要从格式入手。 首先学习好单行注释和多行注释。 先学习简单整数的打印输出&#xff0c;主要学习echo命令&#xff0c;学习选项-e -n的使用。 下面的练习是常用的两个分支跳转程序&#xff1a;break和continue。 #!/bin/bash # 这是单…