【记录】 uniapp 消息聊天页 --面点之家小程序

962 阅读4分钟

记录面点之家小程序的聊天对话页

<template>
	<view>
		<view class="tips color_fff size_12 align_c" :class="{ show: ajax.loading }" @tap="getHistoryMsg">{{ ajax.loadText }}</view>
		<view class="box-1" id="list-box">
			<view class="talk-list">
				<view v-for="(item, index) in talkList" :key="index" :id="`msg-${item.id}`">
					<view class="item flex_col" :class="item.type == 1 ? 'push' : 'pull'">
						<image :src="item.sender.avatar" mode="aspectFill" class="pic"></image>
						<view class="content">{{ item.content }}</view>
					</view>
				</view>
			</view>
		</view>
		<view class="box-2">
			<view class="flex_col">
				<view class="flex_grow">
					<input type="text" maxlength="256" class="content" v-model="content" placeholder="请输入聊天内容" placeholder-style="color:#DDD;" :cursor-spacing="6" />
				</view>
				<button class="send" @tap="send">发送</button>
			</view>
		</view>
	</view>
</template>

<script>
export default {
	data() {
		return {
			talkList: [],
			ajax: {
				rows: 20, //每页数量
				pageNum: 1, //页码
				flag: true, // 请求开关
				loading: true, // 加载中
				loadText: '正在获取消息'
			},

			//我们的字段
			buddyId: '',
			content: '',
			friendId: '',
			postId: '',
			currentDateTime: '',
			avatar: '' //我的头像
		};
	},
	onLoad(option) {
		if (option.postId) {
			this.postId = option.postId;
		}
		if (option.friendId) {
			this.friendId = option.friendId;
		}
		if (option.buddyId) {
			this.buddyId = option.buddyId;
		}
	},
	mounted() {
		this.$nextTick(() => {
			this.getHistoryMsg();
			this.getAvatar();
		});
	},
	onPageScroll(e) {
		if (e.scrollTop < 5) {
			this.getHistoryMsg();
		}
	},
	methods: {
		//获取自己的头像
		getAvatar() {
			this.$Request
				.get({
					url: '/mp/auth/user/get'
				})
				.then(res => {
					this.avatar = res.avatar;
				});
		},
		// 获取历史消息
		getHistoryMsg() {
			if (!this.ajax.flag) {
				return; //
			}
			// 此处用到 ES7 的 async/await 知识,为使代码更加优美。不懂的请自行学习。
			let get = async () => {
				this.hideLoadTips();
				this.ajax.flag = false;
				let data = await this.joinHistoryMsg();

				console.log('----- 历史记录 -----');
				console.log(data); // 查看请求返回的数据结构

				// 获取待滚动元素选择器,解决插入数据后,滚动条定位时使用
				let selector = '';

				if (this.ajax.pageNum > 1) {
					// 非第一页,则取历史消息数据的第一条信息元素
					if(this.talkList.length>0){
						selector = `#msg-${this.talkList[0].id}`;
					}
				} else {
					if(data.length>0){
						// 第一页,则取当前消息数据的最后一条信息元素
						selector = `#msg-${data[data.length - 1].id}`;
					}
				}

				// 将获取到的消息数据合并到消息数组中
				this.talkList = [...data, ...this.talkList];
				
				// 数据挂载后执行,不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
				this.$nextTick(() => {
					// 设置当前滚动的位置
					this.setPageScrollTo(selector);
					this.hideLoadTips(true);

					if (data.length < this.ajax.rows) {
						// 当前消息数据条数小于请求要求条数时,则无更多消息,不再允许请求。
						// 可在此处编写无更多消息数据时的逻辑
					} else {
						this.ajax.pageNum++;

						// 延迟 200ms ,以保证设置窗口滚动已完成
						setTimeout(() => {
							this.ajax.flag = true;
						}, 200);
					}
				});
			};
			get();
		},
		// 历史记录接口
		joinHistoryMsg() {
			// 此处用到 ES6 的 Promise 知识,不懂的请自行学习。
			return new Promise((done, fail) => {
				// 无数据请求接口,由 setTimeout 模拟,正式项目替换为 ajax 即可。
				let arr = [];
				let data = {
					buddyId: this.buddyId,
					friendId: this.friendId,
					postId: this.postId,
					pageNum: this.ajax.pageNum,
					currentDateTime: this.currentDateTime
				};

				this.$Request
					.get({
						url: '/mp/auth/postLetter/detail',
						data
					})
					.then(res => {
						this.currentDateTime = res.currentDateTime;
						this.buddyId = res.buddyId;
						arr = res.list;
						arr.reverse();
						done(arr);
						if (arr.length > 0) {
							this.read();
						}
					});
			});
		},
		//阅读
		read() {
			let data = {
				buddyId: this.buddyId,
				currentDateTime: this.currentDateTime
			};
			this.$Request
				.post({
					url: '/mp/auth/postLetter/read',
					data
				})
				.then(res => {
					console.log(res);
				});
		},
		// 设置页面滚动位置
		setPageScrollTo(selector) {
			let view = uni
				.createSelectorQuery()
				.in(this)
				.select(selector);
			view.boundingClientRect(res => {
				uni.pageScrollTo({
					scrollTop: res.top - 30, // -30 为多显示出大半个消息的高度,示意上面还有信息。
					duration: 0
				});
			}).exec();
		},
		// 隐藏加载提示
		hideLoadTips(flag) {
			if (flag) {
				this.ajax.loadText = '消息获取成功';
				setTimeout(() => {
					this.ajax.loading = false;
				}, 300);
			} else {
				this.ajax.loading = true;
				this.ajax.loadText = '正在获取消息';
			}
		},
		// 发送信息
		send() {
			if (!this.content) {
				uni.showToast({
					title: '请输入有效的内容',
					icon: 'none'
				});
				return;
			}

			uni.showLoading({
				title: '正在发送'
			});
			uni.hideLoading();

			let data = {
				buddyId: this.buddyId?this.buddyId:0,
				content: this.content,
				friendId: this.friendId?this.friendId:0,
				postId: this.postId?this.postId:0
			};

			let contentData = this.content;
			this.$Request
				.post({
					url: '/mp/auth/postLetter/send',
					data
				})
				.then(data => {
					console.log(data);
					// 将当前发送信息 添加到消息列表。
					let dataInfo = {
						content: contentData,
						type: 1,
						sender: {
							avatar: this.avatar
						}
					};

					this.talkList.push(dataInfo);
					
					this.$nextTick(() => {
						// 清空内容框中的内容
						this.content = '';
						uni.pageScrollTo({
							scrollTop: 999999, // 设置一个超大值,以保证滚动条滚动到底部
							duration: 0
						});
					});
					
				});

			
		}
	}
};
</script>

<style lang="scss">
@import './global.scss';
page {
	background-color: #f3f3f3;
	font-size: 28rpx;
}

/* 加载数据提示 */
.tips {
	position: fixed;
	left: 0;
	top: var(--window-top);
	width: 100%;
	z-index: 9;
	background-color: rgba(0, 0, 0, 0.15);
	height: 72rpx;
	line-height: 72rpx;
	transform: translateY(-80rpx);
	transition: transform 0.3s ease-in-out 0s;

	&.show {
		transform: translateY(0);
	}
}

.box-1 {
	width: 100%;
	height: auto;
	padding-bottom: 100rpx;
	box-sizing: content-box;

	/* 兼容iPhoneX */
	margin-bottom: 0;
	margin-bottom: constant(safe-area-inset-bottom);
	margin-bottom: env(safe-area-inset-bottom);
}
.box-2 {
	position: fixed;
	left: 0;
	width: 100%;
	bottom: 0;
	height: auto;
	z-index: 2;
	border-top: #e5e5e5 solid 1px;
	box-sizing: content-box;
	background-color: #f3f3f3;

	/* 兼容iPhoneX */
	padding-bottom: 0;
	padding-bottom: constant(safe-area-inset-bottom);
	padding-bottom: env(safe-area-inset-bottom);

	> view {
		padding: 0 20rpx;
		height: 100rpx;
	}

	.content {
		background-color: #fff;
		height: 64rpx;
		padding: 0 20rpx;
		border-radius: 32rpx;
		font-size: 28rpx;
	}

	.send {
		background-color: #42b983;
		color: #fff;
		height: 64rpx;
		margin-left: 20rpx;
		border-radius: 32rpx;
		padding: 0;
		width: 120rpx;
		line-height: 62rpx;

		&:active {
			background-color: #5fc496;
		}
	}
}

.talk-list {
	padding-bottom: 20rpx;

	/* 消息项,基础类 */
	.item {
		padding: 20rpx 20rpx 0 20rpx;
		align-items: flex-start;
		align-content: flex-start;
		color: #333;

		.pic {
			width: 92rpx;
			height: 92rpx;
			border-radius: 50%;
			border: #fff solid 1px;
		}

		.content {
			padding: 20rpx;
			border-radius: 4px;
			max-width: 500rpx;
			word-break: break-all;
			line-height: 52rpx;
			position: relative;
		}

		/* 收到的消息 */
		&.pull {
			.content {
				margin-left: 32rpx;
				background-color: #fff;

				&::after {
					content: '';
					display: block;
					width: 0;
					height: 0;
					border-top: 16rpx solid transparent;
					border-bottom: 16rpx solid transparent;
					border-right: 20rpx solid #fff;
					position: absolute;
					top: 30rpx;
					left: -18rpx;
				}
			}
		}

		/* 发出的消息 */
		&.push {
			/* 主轴为水平方向,起点在右端。使不修改DOM结构,也能改变元素排列顺序 */
			flex-direction: row-reverse;

			.content {
				margin-right: 32rpx;
				background-color: #a0e959;

				&::after {
					content: '';
					display: block;
					width: 0;
					height: 0;
					border-top: 16rpx solid transparent;
					border-bottom: 16rpx solid transparent;
					border-left: 20rpx solid #a0e959;
					position: absolute;
					top: 30rpx;
					right: -18rpx;
				}
			}
		}
	}
}
</style>

//global.scss
/* 根元素样式 设置页面背景、字体大小、字体颜色,字符间距、长单词换行 */
page {
  background-color: #f3f3f3;
  font-size: 28rpx;
  box-sizing: border-box;
  color: #333;
  letter-spacing: 0;
  word-wrap: break-word;
}

/* 设置常用元素尺寸规则 */
view,textarea,input,label,form,button,image{box-sizing: border-box;}
/* 按钮样式处理 */
button{font-size: 28rpx;}
/* 取消按钮默认的边框线效果 */
button:after{border:none;}
/* 设置图片默认样式,取消默认尺寸 */
image{display: block;height: auto;width: auto;}
/* 输入框默认字体大小 */
textarea,input{font-size: 28rpx;};

/* 列式弹性盒子 */
.flex_col {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: flex-start;
  align-items: center;
  align-content: center;
}
/* 行式弹性盒子 */
.flex_row {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  justify-content: flex-start;
  align-items: flex-start;
  align-content: flex-start;
}
 
/* 弹性盒子弹性容器 */
.flex_col .flex_grow{width:0;flex-grow: 1;}
.flex_row .flex_grow{flex-grow: 1;}
 
/* 弹性盒子允许换行 */
.flex_col.flex_wrap{flex-wrap: wrap;}
 
/* 弹性盒子居中对齐 */
.flex_col.flex_center,.flex_row.flex_center{justify-content: center;}
 
/* 列式弹性盒子两端对齐 */
.flex_col.flex_space{justify-content: space-between;}

/* 弹性盒子快速分栏 ,这里非常郁闷 uniapp 居然不支持 * 选择器 */
.flex_col.flex_col_2>view{width: 50%;}
.flex_col.flex_col_3>view{width: 33.33333%;}
.flex_col.flex_col_4>view{width: 25%;}
.flex_col.flex_col_5>view{width: 20%;}
.flex_col.flex_col_6>view{width: 16.66666%;}

/* 字体颜色 */
.color_333 {color: #333;}
.color_666 {color: #666;}
.color_999 {color: #999;}
.color_ccc {color: #ccc;}
.color_fff {color: #fff;}
.color_6dc{color: #6dca6d;}
.color_d51{color: #d51917;}
.color_09f{color: #0099ff;}
 
/* 背景色*/
.bg_fff{background-color: #ffffff;}
 
/* 字体大小 */
.size_10 {font-size: 20rpx;}
.size_12 {font-size: 24rpx;}
.size_14 {font-size: 28rpx;}
.size_16 {font-size: 32rpx;}
.size_18 {font-size: 36rpx;}
.size_20 {font-size: 40rpx;}
 
/* 字体加粗 */
.font_b{font-weight: bold;}
 
/* 对齐方式 */
.align_c{text-align: center;}
.align_l{text-align: left;}
.align_r{text-align: right;}
 
/* 遮罩 */
.shade{
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0,0,0,0.8);
  z-index: 100;
}
 
/* 弹窗 */
.shade_box{
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  z-index: 101;
  min-width: 200rpx;
  min-height: 200rpx;
}