# 一、连接即时通讯websocket

# 1. 在初始化登录的时候链接

关于websocket的链接,uni-app官网:https://uniapp.dcloud.net.cn/api/request/websocket.html (opens new window)
/store/modules/chatuser.js文件

import {requestUrl} from '@/common/mixins/configData.js';
export default {
	// 对应的mapState,在computed中引用导入
	// 类似于data,把全局或者公共部分放在这里
	state: {
		...
	},
	// 异步的方法,在methods引入
	actions: {
		//注册登录后续的操作
		...,
		// 用户退出登录
		...,
		// 初始化登录注册状态(避免刷新页面state没有数据)
		initChatuserAction({commit,state,dispatch}) {
			console.log('初始化登录注册状态', state);
			// 给state赋值
			let user = uni.getStorageSync('chatuser') ?
				JSON.parse(uni.getStorageSync('chatuser')) : '';
			if (user) {
				state.regloginUser = user;
				console.log('初始化登录注册状态', state);
				//获取好友申请的信息
				dispatch('getGoodfriendapply');
				// 连接websocket服务
				dispatch('connectWebsocket',user);
			}
		},
		//获取好友申请的信息
		...,
		// 获取好友列表
		...,
		// 连接websocket服务
		connectWebsocket({commit,state,dispatch}, payload = {}){
			// 链接即时通讯websocket
			let chatSocket = uni.connectSocket({
				//http://192.168.2.7:7001
				//ws://192.168.2.7:7001/ws
				url: 'ws' + requestUrl.http.replace('http','').replace('s','') + `/ws?
                token=${payload.token}`,
				complete: () => {},
				timeout: 10000 // 10秒超时
			});
			// 心跳
			let heartbeatTimer = null;
			//链接成功
			chatSocket.onOpen(() => {
			    console.log('websocket链接成功');
			  
			    // 启动心跳
				heartbeatTimer = setInterval(() => {
					if (chatSocket && chatSocket.readyState === 1) {
					  chatSocket.send(JSON.stringify({ type: 'ping' }));
					}
				}, 25000); // 每25秒发送一次心跳
			});
			// 接收信息
			chatSocket.onMessage(res => {
				// 处理不同平台的消息格式
				try {
					const data = typeof res.data === 'string' 
						? JSON.parse(res.data) 
						: res.data;
						
					console.log('websocket接收信息', data);
				
					if (data.type === 'ping') {
						// 响应心跳
						chatSocket.send(JSON.stringify({ type: 'pong' }));
					}
				} catch (e) {
					console.error('消息解析错误', e);
				}
			});
			// 断开连接
			chatSocket.onClose((res) => {
			    console.log('断开连接原因:', res);
			    clearInterval(heartbeatTimer); // 清除心跳
				heartbeatTimer = null;
			  
			    // 尝试重新连接
				setTimeout(() => {
					console.log('尝试重新连接 WebSocket');
					dispatch('initChatuserAction');
				}, 3000);
			});
			// 错误处理
			chatSocket.onError(err => {
				console.error('WebSocket 错误:', err);
				clearInterval(heartbeatTimer);
				heartbeatTimer = null;
				setTimeout(() => {
					console.log('错误处理尝试重新连接 WebSocket');
					dispatch('initChatuserAction');
				}, 5000);
			});
		},
	},
}

# 2. 调用类的形式执行websocket

我们可以编写一个类来调用websocket,方便我们后期扩展

# 1. 新建js类文件: /common/js/chatClass.js

import {requestUrl} from '@/common/mixins/configData.js';
class chatClass {
	//构造函数
	constructor(){
		// ws地址
		this.url = '';
		if(requestUrl.http.startsWith('http')){
			this.url = 'ws' + requestUrl.http.replace('http','');
		}else if(requestUrl.http.startsWith('https')){
			this.url = 'wss' + requestUrl.http.replace('https','');
		}
		// 是否上线
		this.isOnline = false;
		// socketTask 对象
		this.chatSocket = null;
		// 我的信息vuex或者本地获取
		const user = uni.getStorageSync('chatuser') ?
		JSON.parse(uni.getStorageSync('chatuser')) : '';
		this.user = user;
		// 连接websocket
		if(this.user && this.user.token){
			this.connectSocket();
		}
		// 心跳
		this.heartbeatTimer = null;
	}
	// 连接websocket
	connectSocket(){
		// 链接即时通讯websocket
		this.chatSocket = uni.connectSocket({
			//http://192.168.2.7:7001
			//ws://192.168.2.7:7001/ws
			url: this.url + `/ws?token=${this.user.token}`,
			complete: () => {},
			timeout: 10000 // 10秒超时
		});
		// 心跳
		//let heartbeatTimer;
		//链接成功
		this.chatSocket.onOpen(() => {
		    console.log('websocket链接成功');
		  
		    // 启动心跳
			this.heartbeatTimer = setInterval(() => {
				//(跨平台兼容)小程序环境中没有 WebSocket.OPEN,使用数字 1 表示 OPEN 状态
				if (this.chatSocket && this.chatSocket.readyState === 1) {
				  this.chatSocket.send(JSON.stringify({ type: 'ping' }));
				}
			}, 25000); // 每25秒发送一次心跳
		});
		// 接收信息
		this.chatSocket.onMessage(res => {
			// 处理不同平台的消息格式
		    try {
			    const data = typeof res.data === 'string' 
					? JSON.parse(res.data) 
					: res.data;
					
				console.log('websocket接收信息', data);
			
			    if (data.type === 'ping') {
					// 响应心跳
					this.chatSocket.send(JSON.stringify({ type: 'pong' }));
				}
		    } catch (e) {
			    console.error('消息解析错误', e);
		    }
		});
		// 断开连接
		this.chatSocket.onClose((res) => {
		    console.log('断开连接原因:', res);
		    clearInterval(this.heartbeatTimer); // 清除心跳
		    this.heartbeatTimer = null;
		  
		    // 尝试重新连接
			setTimeout(() => {
				console.log('尝试重新连接 WebSocket');
				//dispatch('initChatuserAction');
				this.connectSocket();
			}, 3000);
		});
		// 错误处理
		this.chatSocket.onError(err => {
			console.error('WebSocket 错误:', err);
			clearInterval(this.heartbeatTimer);
			this.heartbeatTimer = null;
			setTimeout(() => this.connectSocket(), 5000);
		});
	}
}

export default chatClass;

# 2. 在vuex中调用

/store/modules/chatuser.js 中代码

...
import chatClass  from '@/common/js/chatClass.js';
export default {
	// 对应的mapState,在computed中引用导入
	// 类似于data,把全局或者公共部分放在这里
	state: {
		...
	},
	// 异步的方法,在methods引入
	actions: {
		//注册登录后续的操作
		...,
		// 用户退出登录
		...,
		// 初始化登录注册状态(避免刷新页面state没有数据)
		initChatuserAction({commit,state,dispatch}) {
			console.log('初始化登录注册状态', state);
			// 给state赋值
			let user = uni.getStorageSync('chatuser') ?
				JSON.parse(uni.getStorageSync('chatuser')) : '';
			if (user) {
				state.regloginUser = user;
				console.log('初始化登录注册状态', state);
				//获取好友申请的信息
				dispatch('getGoodfriendapply');
				// 连接websocket服务
				// dispatch('connectWebsocket',user);
				// 实例化后自动执行构造函数
				new chatClass(); 
			}
		},
		//获取好友申请的信息
		...,
		// 获取好友列表
		...,
		// 连接websocket服务
		connectWebsocket({commit,state,dispatch}, payload = {}){
			...
		},
	},
}

# 二、通讯录好友列表进入聊天页

# 1. 聊天类扩展几个方法

在文件 /common/js/chatClass.js

import {requestUrl} from '@/common/mixins/configData.js';
class chatClass {
	// 构造函数
	constructor() {
	    // ws地址
		this.url = '';
		if(requestUrl.http.startsWith('http')){
			this.url = 'ws' + requestUrl.http.replace('http','');
		}else if(requestUrl.http.startsWith('https')){
			this.url = 'wss' + requestUrl.http.replace('https','');
		}
		// 是否上线
		this.isOnline = false;
		// socketTask对象
		this.chatSocket = null;
		// 我的信息 vuex 或者从本地获取
		let user = uni.getStorageSync('chatuser') ?
			JSON.parse(uni.getStorageSync('chatuser')) : '';
		this.user = user;
		// 连接websocket
		if(this.user && this.user.token){
			this.connectSocket();
		}
		// 心跳
		this.heartbeatTimer = null;
	}
	
	// 连接websocket
	connectSocket(){
		this.chatSocket = uni.connectSocket({
			// http://192.168.2.7:7001
			// ws://192.168.2.7:7001/ws
			// https://lesson07.51yrc.com
			// wss://lesson07.51yrc.com/ws
			url: this.url + `/ws?token=${this.user.token}`,
			complete: ()=> {},
			timeout:10000, // 10秒超时
		});
		
		// 连接成功
		this.chatSocket.onOpen(()=>{
			console.log('websocket连接成功');
			//调用方法
			this.onOpen();
			//启动心跳
			this.heartbeatTimer = setInterval(()=>{
				if(this.chatSocket && this.chatSocket.readyState === 1){
					this.chatSocket.send(JSON.stringify({type:'ping'}));
				}
			},25000); // 每隔25秒发送一次心跳
		});
		// 接收信息
		this.chatSocket.onMessage(res=>{
			// 处理一下不同平台消息格式
			try{
				const data = typeof res.data === 'string' ?
				JSON.parse(res.data) : res.data;
				
				this.onMessage(data);
				
				if(data.type === 'ping'){
					// 响应心跳
					this.chatSocket.send(JSON.stringify({type:'pong'}));
				}
			}catch(e){
				console.error('接收信息错误',e);
			}
		});
		// 断开连接
		this.chatSocket.onClose(res=>{
			console.log('断开连接的原因', res);
			clearInterval(this.heartbeatTimer); // 清除心跳
			this.heartbeatTimer = null;
			// 调用方法
			this.onClose();
			// 尝试重新连接
			setTimeout(()=>{
				console.log('断开连接尝试重新连接websocket');
				//dispatch('initChatuserAction');
				this.connectSocket();
			},3000);
		});
		// 错误处理
		this.chatSocket.onError(err=>{
			console.error('websocket 错误:', err);
			clearInterval(this.heartbeatTimer); // 清除心跳
			this.heartbeatTimer = null;
			// 尝试重新连接
			setTimeout(()=>{
				console.log('错误处理尝试重新连接websocket');
				//dispatch('initChatuserAction');
				this.connectSocket();
			},5000);
		});
	}
	
	// 连接成功
	onOpen(){
		// 上线用户
		this.isOnline = true;
	}
	// 断开连接
	onClose(){
		// 下线用户
		this.isOnline = false;
		this.chatSocket = null;
	}
	// 接收信息
	onMessage(data){
		console.log('websocket接收信息',data);
	}
	
	// 关闭链接
	close(){
		// 用户退出关闭链接
		// 调用socketTask 对象的close方法
		this.chatSocket.close();
	}
}

export default chatClass;

# 2. vuex中调用

在文件 /store/modules/chatuser.js

...
export default {
	// 对应的mapState,在computed中引用导入
	// 类似于data,把全局或者公共部分放在这里
	state: {
		...
		// 聊天类
		chatClass:null,
	},
	// 异步的方法,在methods引入
	actions: {
		//注册登录后续的操作
		regloginAction({commit,state,dispatch}, regloginRes) {
			...
			//获取好友申请的信息
			...
			//连接websocket服务
			//dispatch('connectWebsocket',state.regloginUser);
			state.chatClass = new chatClass();
		},
		// 用户退出登录
		logoutAction({commit,state}) {
			//关闭链接websocket
			state.chatClass.close();
			state.chatClass = null;
			//清空用户信息
			...
			// 删除本地存储
			...
		},
		// 初始化登录注册状态(避免刷新页面state没有数据)
		initChatuserAction({commit,state,dispatch}) {
			console.log('初始化登录注册状态', state);
			// 给state赋值
			...
			if (user) {
				...
				//连接websocket服务
				//dispatch('connectWebsocket',user);
				state.chatClass = new chatClass();
			}
		},
		//获取好友申请的信息
		...,
		// 获取好友列表
		...,
		
	},
}

# 3. 先从我的好友列表(通讯录)进入聊天

在页面 /pages/userinfo/userinfo.vue

//发消息
sendMessageFun(){
	console.log('发消息的逻辑',this.me);
	if(this.me.ismygoodfriend){
		console.log('是我的朋友,可以直接聊天了',this.user);
		// 进入聊天页,传一些用户数据过去在页面展示
		const userchat = {
			id: this.user.id,
			name: this.user.nickname || this.user.username,
			avatar: this.user.avatar,
			chatType: 'single' , // 单聊 single 群聊 group
		};
		uni.navigateTo({
			url: `/pages/chat/chat?arg=${encodeURIComponent(JSON.stringify(userchat))}`,
		});
	}else{
		...
	}
},

# 4. 聊天页面初始判断

在页面 /pages/chat/chat.nvue

data() {
	return {
		...
		// 页面参数
		arg:null,
	}
},
onLoad(e) {
	if(!e.arg || !this.me) return this.navigateBack();
	let arg = decodeURIComponent(e.arg);
	try{
		this.arg = JSON.parse(arg);
		console.log('聊天页参数',this.arg);
	}catch{
		return this.navigateBack();
	}
},
computed:{
	...mapState({
		...,
		me : state => state.Chatuser.regloginUser,
	}),
},

# 三、创建和销毁聊天对象消息及给服务器发消息测试

课后把后台是不是我的好友的返回结果调整了一下,由goodfriend 改成了 好友信息,具体:十九、查询一下对方是否是我的好友

# 1. 重新处理用户信息昵称显示

在页面 /pages/userinfo/userinfo.vue

// 是不是我的好友
ismygoodfriend(){
	uni.$u.http.post(requestUrl.http + `/api/chat/ismygoodfriend/${this.user.id}`,{},{
		header:{
			token:uni.getStorageSync('chatuser_token'),
		}
	}).then(res => {
			console.log('是不是我的好友',res);
			if(res.data.msg == 'ok') {
				this.me.ismygoodfriend = res.data.data;
				this.user.nickname = res.data.data.nickname;
				uni.setNavigationBarTitle({
				   title: this.user.nickname || this.user.username,
				});
			}
	}).catch(err => {
		this.me.ismygoodfriend = false;
	});
},

# 2. 聊天类创建聊天对象及发消息到服务器

给服务器发消息(单聊)(发送消息给对方)的接口说明:二十、给服务器发消息(单聊)(发送消息给对方)
在文件 /common/js/chatClass.js

import {requestUrl} from '@/common/mixins/configData.js';
class chatClass {
	// 构造函数
	constructor() {
	    ...
		// 聊天对象信息
		this.ToObject = false;
	}
	
	// 连接websocket
	...
	
	// 连接成功
	...
	
	// 断开连接
	...
	
	// 接收信息
	...
	
	// 关闭链接
	...
	
	// 创建聊天对象信息
	createChatToObject(arg){
		this.ToObject = arg;
		console.log('创建聊天对象信息',this.ToObject);
	}
	
	// 销毁聊天对象信息
	destroyChatToObject(){
		this.ToObject = false;
	}
	
	// 发送消息
	sendmessage(msg){
		return new Promise((result,reject)=>{
			uni.$u.http.post(requestUrl.http + `/api/chat/socket/sendmessage`,{
				sendto_id: this.ToObject.id,
				chatType: this.ToObject.chatType,// 单聊 single 群聊 group
				type: msg.type,
				data: msg.data,
			},{
			    header:{
				    token:uni.getStorageSync('chatuser_token'),
			    }
			}).then(res => {
				 console.log('发送消息结果', res);
				 result(res);
			}).catch(err =>{
				console.log('发送消息失败', err);
				reject(err);
			});
		});
	}
}

export default chatClass;

# 3.在聊天页创建和销毁聊天对象信息

在页面 /pages/chat/chat.nvue

onLoad(e) {
	if(!e.arg) return this.navigateBack();
	let arg = decodeURIComponent(e.arg);
	try{
		this.arg = JSON.parse(arg);
		console.log('聊天页参数对象',this.arg);
		// 创建聊天对象信息
		this.chatClass.createChatToObject(this.arg);
	}catch{
		return this.navigateBack();
	}
},
destroyed() {
	// 销毁聊天对象信息
	this.chatClass.destroyChatToObject();
},
computed:{
	...mapState({
		...,
		chatClass:state=>state.Chatuser.chatClass,
	}),
},

# 4. 给服务器发消息(单聊)(发送消息给对方)测试

给服务器发消息(单聊)(发送消息给对方)的接口说明:二十、给服务器发消息(单聊)(发送消息给对方)
在文件 /pages/chat/plusIconAction.js

//发送消息
sendMessage(msgType, option = {}){
	//向服务器发消息
	this.chatClass.sendmessage({
		type: msgType,
		data: this.messageValue, // 测试发送文字
	}).then(res => {
		console.log('页面获取服务器返回结果', res);
	}).catch(err => {
		console.log('页面获取服务器失败结果', err);
	});
	
	return;
	console.log('发送消息',msgType);
	...
}

# 四、完善聊天页发消息的界面展示

# 1. 先解决几个小问题

#我的页面头像显示问题

不是以http开头的头像无法显示,需要处理一下,在页面 /pages/wode/wode.nvue

<script>
	import {requestUrl} from '@/common/mixins/configData.js';
	...
	export default {
		...
		computed:{
			...
			//头像
			avatarShow(){
				let avatar = this.user && this.user.avatar;
				if(!avatar.startsWith('http')){
					avatar = requestUrl.http + avatar;
				}
				return avatar;
			},
			...
		},
		...
	}
</script>

# ② 组件确定聊天我的身份id

在组件 /components/chat-item/chat-item.vue

// 我的判断, 假设我的id=2,后期由实际数据在更换
isMe(){
	let user_id = this.me.id;
	return this.item.user_id === user_id;
},

# 2. 完善聊天页发消息的界面展示

# ① 聊天页清空模拟数据

在页面 /pages/chat/chat.nvue

onLoad(e) {
	...
	
	//模拟数据清空
	this.chatDataList = [];
},

# ② 发消息的数据要跟服务器的数据格式一致

在聊天类 /common/js/chatClass.js

import {requestUrl} from '@/common/mixins/configData.js';
class chatClass {
	// 构造函数
	constructor() {
	    ...
	}
	
	// 连接websocket
	...
	
	// 连接成功
	...
	
	// 断开连接
	...
	
	// 接收信息
	...
	
	// 关闭链接
	...
	
	// 创建聊天对象信息
	...
	
	// 销毁聊天对象信息
	...
	
	// 发送消息(单聊)
	sendmessage(msg){
		return new Promise((result,reject)=>{
			uni.$u.http.post(requestUrl.http + `/api/chat/socket/sendmessage`,{
				...,
				options: encodeURIComponent(JSON.stringify(msg.options)), //选填
			}, {
				...
			}).then(res => {
				...
			}).catch(err => {
				...
			});
		});
	}
	
	// 页面发消息的格式和服务器一致
	formatSendMessage(args){
		return {
			id: 0, // 后端生成的UUID,唯一id, 聊天记录id,方便撤回消息
			from_avatar: this.user.avatar, // 发送者头像
			from_name: this.user.nickname || this.user.username, // 发送者名称
			from_id: this.user.id, // 发送者id
			to_id: this.ToObject.id, // 接收者id
			to_name: this.ToObject.name, // 接收者名称
			to_avatar: this.ToObject.avatar, // 接收者头像
			chatType: this.ToObject.chatType, // 聊天类型 单聊
			type: args.type, // 消息类型
			data: args.data, // 消息内容
			options: args.options ? args.options : {}, // 其它参数
			create_time: (new Date()).getTime(), // 创建时间
			isremove: 0, // 0未撤回 1已撤回
			// 发送状态 pending发送中 success成功 fail失败
			sendStatus: args.sendStatus ? args.sendStatus : "pending" , 
		}
	}
}

export default chatClass;

# ③ 完成发送消息的页面逻辑

在文件 /pages/chat/plusIconAction.js

//发送消息
sendMessage(msgType, option = {}){
	console.log('发送消息',msgType);
	let msg = {
		avatar: 'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-07.png',
		nickname: '小二哥',
		user_id: 2,
		chat_time: (new Date()).getTime(),
		data: '',
		type:msgType, //image,video
		isremove:false,
	};
	switch (msgType){
		...
	}
	// 组织一下消息格式和服务器一致
	let serverMsg = this.chatClass.formatSendMessage({
		type: msgType,
		data: {
			data: msg.data,
			dataType: msg.dataType ? msg.dataType : false,
			otherData : msg.otherData ? msg.otherData : null,
		},
		options: option,
	});
	console.log('发消息的格式信息',serverMsg); //return;
	// 页面上显示信息
	msg.avatar = serverMsg.from_avatar;
	msg.nickname = serverMsg.from_name;
	msg.user_id = serverMsg.from_id;
	// 放到页面进行展示
	this.chatDataList.push(msg);
	// 发给服务器消息
	this.chatClass.sendmessage(serverMsg).then(result => {
		console.log('页面接收服务器返回结果',result);
	}).catch(error => {
		console.log('页面接收服务器错误结果',error);
	});
	// 清空发送的内容然后还要滚动到底部
	if(msgType == 'text') this.messageValue = '';
	this.chatContentToBottom();
	
},

# 五、消息发送状态的处理

# ① 新增消息发送状态的样式

在组件 /components/chat-item/chat-item.vue

<!-- 聊天内容 -->
...
<!-- 给服务器发消息状态 -->
<view v-if="item.sendStatus && item.sendStatus != 'success'"
class="flex align-center justify-end pr-5 pb-5">
	<text class="font-sm"
	:class="[item.sendStatus == 'fail'? 
	'text-danger' : 'text-light-muted']">{{item.sendStatus == 'fail'? '发送失败':'发送中...'}}</text>
</view>
<!-- 弹出菜单 -->
...

# ② 发消息中处理状态

在文件 /pages/chat/plusIconAction.js

//发送消息
sendMessage(msgType, option = {}){
	console.log('发送消息',msgType);
	let msg = {
		...
	};
	switch (msgType){
		...
	}
	// 组织一下消息格式和服务器一致
	let serverMsg = this.chatClass.formatSendMessage({
		...
	});
	// console.log('发消息格式数据',serverMsg); return;
	// 显示到页面上
	msg.avatar = serverMsg.from_avatar;
	msg.nickname = serverMsg.from_name;
	msg.user_id = serverMsg.from_id;
	// 发送成功失败状态处理
	msg.sendStatus = 'pending';
	// 拿到要发送消息的索引实际就是当前聊天列表的长度值
	let sendmsgIndex = this.chatDataList.length;
	// 在页面上显示
	this.chatDataList.push(msg);
	// 发给服务器消息
	this.chatClass.sendmessage(serverMsg).then(result => {
		console.log('页面接收服务器返回结果',result);
		// 拿到刚发送的消息赋值
		this.chatDataList[sendmsgIndex].id = result.id;
		this.chatDataList[sendmsgIndex].sendStatus = 'success';
	}).catch(error => {
		console.log('页面接收服务器错误结果',error);
		this.chatDataList[sendmsgIndex].sendStatus = 'fail';
	});
	// 清空发送的内容然后还要滚动到底部
	...

},

# 六、发消息前把消息添加到本地历史记录,消息页的聊天列表更新一下,发完之后更新一下本地历史记录

说明:

  1. 由于这部分内容比较抽象,我就不手敲了,它的实现流程其实相当于是固定的,因此我这里就直接给大家提供实现这些功能的方法;
  2. 方法里面每行代码我都提供了注释,大家可以根据注释进行代码的阅读;
  3. 大家可以根据注释说明打印相关的变量属性,增加理解;
  4. 大家跟着老师测试这些代码效果即可;

在文件 /common/js/chatClass.js

import {requestUrl} from '@/common/mixins/configData.js';
class chatClass {
	// 构造函数
	constructor() {
	    ...
		//消息页整个聊天列表数据的未读数
		this.xiaoxiNoreadNum = 0;
		
	}
	
	// 连接websocket
	...
	
	// 连接成功
	...
	
	// 断开连接
	...
	
	// 接收信息
	...
	
	// 关闭链接
	...
	
	// 创建聊天对象信息
	...
	
	// 销毁聊天对象信息
	...
	
	// 页面发消息的格式和服务器要一致
	...
	

	// 发送消息(单聊)
	sendmessage(msg) {
		return new Promise((result, reject) => {
			// 把发送的聊天信息存在本地
			let { k } = this.addChatInfo(msg);
			// 消息页的聊天列表更新一下
			this.updateXiaoXiList(msg);
			// 查看我是否在线websocket是否正常连接
			if (!this.meIsOnline()) return reject('我掉线了');
			// 发送消息
			uni.$u.http.post(requestUrl.http + `/api/chat/socket/sendmessage`, {
				sendto_id: this.ToObject.id,
				chatType: this.ToObject.chatType, // 单聊 single 群聊 group
				type: msg.type,
				data: msg.data,
				options: encodeURIComponent(JSON.stringify(msg.options)), // 选填
			}, {
				header: {
					token: uni.getStorageSync('chatuser_token'),
				}
			}).then(res => {
				console.log('发送消息到服务器结果res', res);
				msg.id = res.data.data.id;
				msg.sendStatus = 'success';
				console.log('发送消息成功之后的msg', msg);
				// 更新本地的历史记录 send不用传因为已经发送完成了
				this.updateChatInfo(msg, k);
				// 成功返回给页面
				result(res);
			}).catch(err => {
				//console.log('发送消息到服务器失败', err);
				msg.sendStatus = 'fail';
				// 更新本地的历史记录 send不用传因为已经发送完成了
				this.updateChatInfo(msg, k);
				// 失败返回给页面
				reject(err);
			});
		});
	}
	

	// 把聊天信息存在本地
	addChatInfo(msg, isSend = true) {
		// 存本地key值设计 chatDetail_我的id_单聊群聊_和谁聊接收人(个人还是群)id
		// key:`chatDetail_${this.user.id}_${msg.chatType}_${xx}`
		// 重点分析接收人id
		// 如果是单聊则是用户id,如果是群聊,则是群id(群聊id放在消息to_id中)
		let id = msg.chatType == 'single' ? (isSend ? msg.to_id : msg.from_id) : msg.to_id;
		let key = `chatDetail_${this.user.id}_${msg.chatType}_${id}`;
		// 先获取历史记录
		let list = this.getChatInfo(key);
		console.log('获取历史记录', list);
		// 做个标识,方便之后拿具体某条历史消息
		msg.k = 'k' + list.length;
		// 将消息放入历史记录
		list.push(msg);
		// 重新存历史记录到本地(因为加了新消息)
		uni.setStorageSync(key, JSON.stringify(list));
		// 返回
		return {
			data: msg,
			k: msg.k,
		}
	}
	
	
	// 获取本地历史记录, 传key值则找指定聊天记录
	getChatInfo(key = false) {
		if (!key) {
			// 没有传key 则找当前会话聊天记录
			key = `chatDetail_${this.user.id}_${this.ToObject.chatType}_${this.ToObject.id}`;
		}
		console.log('获取历史记录, 传key值则找指定聊天记录的key,当前key值',key);
		let list = uni.getStorageSync(key);
		// console.log('获取历史记录得到的数据', list);
		if (list) {
			if (typeof list == 'string') {
				list = JSON.parse(list);
			}
		} else {
			list = [];
		}
		return list;
	}

	// 更新指定的本地历史记录信息(不急着更新可以异步)
	async updateChatInfo(msg, k, isSend = true) {
		// 获取原来的历史记录
		// 存本地key值设计 chatDetail_我的id_单聊群聊_和谁聊接收人(个人还是群)id
		// key:`chatDetail_${this.user.id}_${msg.chatType}_${xx}`
		// 重点分析接收人id 
		// isSend = true 代表我是发送人from_id
		// 接收人就是to_id
		// 如果是单聊则是用户id,如果是群聊,则是群id(群聊id放在消息to_id中)
		let id = msg.chatType == 'single' ? (isSend ? msg.to_id : msg.from_id) : msg.to_id; //接收人|群id 
		let key = `chatDetail_${this.user.id}_${msg.chatType}_${id}`;
		console.log('更新指定的历史记录信息key', key);
		// 先获取历史记录
		let list = this.getChatInfo(key);
		console.log('先获取历史记录', list);
		// 根据标识k去查找要更新的历史记录
		let index = list.findIndex(e => e.k == k);
		// 没找到
		if (index == -1) return;
		// 找到了,修改指定消息
		list[index] = msg;
		// 改完之后,整个重新存一下
		console.log('改完之后,整个重新存一下', key, list);
		uni.setStorageSync(key, list);
	}
	

    // 删除消息页指定的本地历史记录信息
	deleteChatInfo(to_id, chatType){
		return new Promise((resolve,reject) => {
			try{
				let xiaoxiList = this.getXiaoXiList();
				// 找到当前聊天
				let index = xiaoxiList.findIndex(v => v.id == to_id && v.chatType == chatType);
				if(index != -1){
					// 找到了
					// 删除这个历史记录
					xiaoxiList.splice(index, 1);
					// 处理完了之后,存储消息页列表本地历史信息
					this.setXiaoXiList(xiaoxiList);
					// 更新(获取)消息页,整个消息列表的未读数(不急可以异步执行)
					this.updateXiaoXiListNoreadNum();
					// 消息页,整个消息列表的数据也存入了vuex中
					// 更新一下vuex中的消息列表的数据
					uni.$emit('updateXiaoXiList', xiaoxiList);
					// 执行后续操作
					return resolve();
				}
			}catch{
				return reject();
			}
		});
	}
	
	
	
	// 删除或者(撤回)(与某个群所有或者某条聊天信息,与某个人的所有或者某条聊天信息)
	clearChatInfo(to_id, chatType, msgid = false, doaction = 'revoke'){
		let key = `chatDetail_${this.user.id}_${chatType}_${to_id}`;
		// 根据key获取信息
		let chatDetail = uni.getStorageSync(key);
		chatDetail = chatDetail && typeof chatDetail == 'string' ? 
		             JSON.parse(chatDetail) : chatDetail;
		console.log('本地根据key获取的聊天详细信息', chatDetail);
		if(msgid){
			// 删除与某人或者某群对话中的某一条聊天记录
			if(chatDetail){
				// 拿到具体的某条聊天信息
				let k = chatDetail.findIndex(v=> v.id === msgid);
				if(k > -1){
					console.log('删除或者(撤回)与某人或者某群对话中的某一条聊天记录', chatDetail[k]);
					if(doaction && doaction == 'delete'){
						// 删除这条记录
						chatDetail.splice(k, 1);
					}else{
						// 撤回操作
						// 将这条记录的属性isremove改成1或者true 就会自动隐藏
						chatDetail[k].isremove = 1; 
					}
					// 重新存储
					uni.setStorageSync(key, chatDetail);
				}
			}
		}else{
			// 删除与某人或者某群对话中的本地所有记录
			uni.removeStorageSync(key);
			console.log('删除与某人或者某群对话中的本地所有记录');
		}
		// 消息页数据重新处理
		return new Promise((resolve,reject) => {
			let xiaoxiList = this.getXiaoXiList();
			// 找到当前聊天
			let index = xiaoxiList.findIndex(v => v.id == to_id && v.chatType == chatType);
			if(index != -1){
				// 找到了
				if(msgid){
					if(chatDetail){
						// 执行后续操作 - 如:更新消息页
						return resolve(index);
					}else{
						return reject();
					}
				}else{
					// 消息页删除这个记录
					xiaoxiList.splice(index, 1);
					// 处理完了之后,存储消息页列表本地历史信息
					this.setXiaoXiList(xiaoxiList);
					// 更新(获取)消息页,整个消息列表的未读数(不急可以异步执行)
					this.updateXiaoXiListNoreadNum();
					// 消息页,整个消息列表的数据也存入了vuex中
					// 更新一下vuex中的消息列表的数据
					uni.$emit('updateXiaoXiList', xiaoxiList);
					// 执行后续操作
					return resolve();
				}
			}
			return reject();
		});
	}
	
	

    // 修改某个对话信息(单聊和群聊, 处理设置功能)
	// [如:置顶、免打扰、是否展示昵称、是否提醒、是否确认进群等]
	updateSomeOneChatItem(someone, updatedata){
		return new Promise((resolve,reject) => {
			let xiaoxiList = this.getXiaoXiList();
			// 找到当前聊天
			let index = xiaoxiList.findIndex(v => v.id == someone.id && 
			v.chatType == someone.chatType);
			if(index != -1){
				// 找到了
				console.log('传递过来的要更新的数据',updatedata);
				// 更新数据
				xiaoxiList[index] = {
					...xiaoxiList[index],
					// 重新赋值
					istop: updatedata.istop, //置顶
					nowarn: updatedata.nowarn, //免打扰
					stongwarn: updatedata.stongwarn, //是否提醒 
					shownickname: updatedata.shownickname, //是否显示群成员昵称
				};
				// 处理完了之后,存储消息页列表本地历史信息
				this.setXiaoXiList(xiaoxiList);
				// 更新(获取)消息页,整个消息列表的未读数(不急可以异步执行)
				// this.updateXiaoXiListNoreadNum();
				// 消息页,整个消息列表的数据也存入了vuex中
				// 更新一下vuex中的消息列表的数据
				uni.$emit('updateXiaoXiList', xiaoxiList);
				// 执行后续操作
				return resolve(xiaoxiList[index]);
			}
			return reject();
		});
	}
	

	// 消息页将某条消息更新成你指定的消息
	XiaoXiListUpdataZhiDingMsg(XiaoXiListId,chatType,msg){
		console.log('---消息页将某条消息更新成你指定的消息id---',XiaoXiListId);
		console.log('---消息页将某条消息更新成你指定的消息内容---',msg);
		// 获取消息页列表本地历史信息(消息页消息列表)
		let list = this.getXiaoXiList();
		console.log('获取消息页列表旧历史', list);
		// 查消息页列表索引
		let index = list.findIndex(v => v.chatType == chatType && v.id == XiaoXiListId);
		if(index != -1){
			// 主要是更新data部分
			let data = msg.data;
			try {
				if(typeof msg.data == 'string'){
					data = JSON.parse(msg.data);
				}
			}catch {
				data = msg.data;
			}
			// 当发送消息是图片视频等,消息页列表最新聊天的最后一条消息显示[图片][视频]等
			let isSend = msg.from_id === this.user.id ? true : false;
			let datadesc = this.XiaoXiListAnyOneLastMsgFormat(data, msg, isSend);
			// 找到了消息列表的数据,将这个数据换成指定的数据
			let findItem = list[index];
			// 更新以下内容:时间 内容 类型等等
			findItem.update_time = msg.create_time;
			findItem.data = data;
			findItem.datadesc = datadesc;
			findItem.type = msg.type;
			// findItem.avatar = avatar; // 消息页的头像不用更新,因为是跟当前聊天对象的记录
			// findItem.name = name; // 消息页的name不用更新,因为是跟当前聊天对象的记录
			findItem.isremove = msg.isremove;
			
			
			// 处理完了之后,存储消息页列表本地历史信息
			this.setXiaoXiList(list);
			// 更新(获取)消息页,整个消息列表的未读数(不急可以异步执行)
			this.updateXiaoXiListNoreadNum();
			// 消息页,整个消息列表的数据也存入了vuex中
			// 更新一下vuex中的消息列表的数据
			uni.$emit('updateXiaoXiList', list);
		}
	}
	


	// 消息页的聊天列表更新一下
	updateXiaoXiList(msg, isSend = true) {
		console.log('消息页最新的一条消息',msg);
		// 获取消息页列表本地历史信息(消息页消息列表)
		let list = this.getXiaoXiList();
		console.log('获取消息页列表旧历史', list);
		// 判断是不是正在和对方聊天,正在聊天页
		// 如果正在聊天页,就没有必要更新消息页的列表了
		let isCurrentChat = false; // 默认不在聊天页
		// 消息页每条数据需要配合服务器的数据,(消息页消息列表)大概有这些字段
		/*
		{
			// 单聊
			id: `用户|群id`,
			avatar: `用户|群头像`,
			name: `用户|群昵称`,
			chatType:'单聊|群聊',
			update_time: '最新的时间',
			data: '最新一条消息',
			type:'最新一条消息类型',
			noreadnum:'未读数',
			istop:'置顶情况',
			shownickname:'是否展示昵称',
			nowarn:'消息免打扰',
			stongwarn: '消息提醒'
			
			// 群聊还有以下字段
			user_id: '群管理员id',
			remark:'群公告',
			invite_confirm:'确认进群'
			...
		}
		*/
		// 重点处理上面的这几个字段
		let id = 0; //接收人|群 id
		let avatar = ''; //接收人|群 头像
		let name = ''; // 接收人|群 称呼
		// 先判断是单聊还是群聊
		if (msg.chatType == 'single') {
			//单聊 
			//先看聊天对象是否存在
			/*
			if(this.ToObject){
				// 存在聊天对象则在聊天页根据isSend判断
				// isSend为true 则我是发送者
				// 则这条消息msg.to_id 就是接收人的id 与聊天人 this.ToObject.id
				// 如果二者相等,则说明正在跟当前接收人聊天
				// 那么消息页就不用更新当前聊天人的信息比如提示发了几条消息等
				isCurrentChat = isSend ? this.ToObject.id === msg.to_id :
				// 否则我不是发送者刚好反过来
				this.ToObject.id === msg.from_id;
			}else{
				// 不存在聊天对象肯定就不在聊天页
				isCurrentChat = false;
			} */
			isCurrentChat = this.ToObject ? isSend ? this.ToObject.id === msg.to_id :
				this.ToObject.id === msg.from_id : false;
			// 处理 接收人|群 id avatar name
			id = isSend ? msg.to_id : msg.from_id;
			avatar = isSend ? msg.to_avatar : msg.from_avatar;
			name = isSend ? msg.to_name : msg.from_name;
	
		} else if (msg.chatType == 'group') {
			//群聊
			//先看聊天对象是否存在
			isCurrentChat = this.ToObject && this.ToObject.id === msg.to_id;
			// 处理 接收人|群 id avatar name
			id = msg.to_id ;
			avatar = msg.to_avatar ;
			name = msg.to_name ;
		}
	
		// 接下来看消息页消息列表是否存在跟当前聊天人的对话
		let index = list.findIndex(v => {
			// 查消息类型和接收人聊天人id
			return v.chatType == msg.chatType && v.id == id;
		});
		// 最后把消息页最新聊天的最后一条消息展示处理一下
		// let data = typeof msg.data == 'string' ? JSON.parse(msg.data) : msg.data;
		let data = msg.data;
		try {
			if(typeof msg.data == 'string'){
				data = JSON.parse(msg.data);
			}
		}catch {
			data = msg.data;
		}
		// 当发送消息是图片视频等,消息页列表最新聊天的最后一条消息显示[图片][视频]等
		let datadesc = this.XiaoXiListAnyOneLastMsgFormat(data, msg, isSend);
		
		// 字段noreadnum 未读消息数量判断
		// isSend为true说明现在处于聊天页
		// 处于聊天页或者聊天当中,那么消息页,聊天列表就没必要+1
		let noreadnum = (isSend || isCurrentChat) ? 0 : 1;
		// 看能不能查到跟当前聊天人的对话
		if (index == -1) {
			// 如果查不到,则新建一个跟当前聊天人的对话信息放到消息页列表最上面
			// 新建对话信息
			// 单聊
			let chatItem = {
				id: id,
				avatar: avatar,
				name: name,
				chatType: msg.chatType,
				update_time: (new Date()).getTime(),
				data: data,
				datadesc: datadesc,
				type: msg.type,
				noreadnum: noreadnum,
				istop: false, //是否置顶
				shownickname: 0, //是否显示昵称
				nowarn: 0, //消息免打扰
				stongwarn: 0, //是否提示来消息了
				isremove: msg.isremove, // 是否撤回
				redirect: msg.redirect ? msg.redirect : false, // 跳转字段
			};
			// 群聊
			if (msg.chatType == 'group') {
				console.log('群聊此时的消息处理', msg);
				chatItem = {
					...chatItem,
					user_id: msg.group && msg.group.user_id ? msg.group.user_id : 0, //群主
					remark: msg.group && msg.group.remark ? msg.group.remark : '', // 群公告
					// 是否需要管理员确认才能进群 默认不需要0
					invite_confirm: msg.group && msg.group.invite_confirm ? msg.group.invite_confirm : 0, 
					// 是否显示群成员昵称
					shownickname: true, //群聊默认显示
				}
			}
			// 放在最上面
			list = [chatItem, ...list];
		} else {
			// 查到了,则更新消息页,消息列表中的这条对话信息让它是最新的
			let findItem = list[index];
			// 则更新以下内容:时间 内容 类型等等
			findItem.update_time = (new Date()).getTime();
			findItem.data = data;
			findItem.datadesc = datadesc;
			findItem.type = msg.type;
			findItem.avatar = avatar;
			findItem.name = name;
			findItem.isremove = msg.isremove;
			findItem.redirect = msg.redirect ? msg.redirect : false;
			// 未读数更新
			findItem.noreadnum += noreadnum;
			console.log('查到了,则更新消息页最新一条消息', findItem);
			// 把这条消息放在消息页,消息列表最上面
			list = this.arrToFirst(list, index);
		}
	
		// 重新存一下 存储消息页列表本地历史信息
		this.setXiaoXiList(list);
	
		// 更新(获取)消息页,整个消息列表的未读数
		this.updateXiaoXiListNoreadNum(list);
	
		// 消息页,整个消息列表的数据也存入了vuex中
		// 更新一下vuex中的消息列表的数据
		uni.$emit('updateXiaoXiList', list);
		// 最后返回
		console.log('获取或更新消息页列表为最新数据', list);
		return list;
	
	}
	
	
	
	// 当发送消息是图片视频等,消息页列表最新聊天的最后一条消息显示[图片][视频]等
	XiaoXiListAnyOneLastMsgFormat(data, msg, isSend){
		console.log('消息页显示[图片][视频]等的data处理数据',data);
		// 显示到消息列表的新属性
		let datadesc = ``;
		let dataType = data.dataType ? data.dataType : msg.dataType;
		switch(dataType){
			case 'image':
			    if(data && data.otherData && data.otherData.type){
				    if(data.otherData.type == 'iconMenus'){
					    datadesc = `[表情]`;
						if(data.otherData.typedata && data.otherData.typedata.name){
							datadesc += `[${data.otherData.typedata.name}]`;
						}
				    }else if(data.otherData.type == 'image'){
						datadesc = `[图片]`;
					}
			    }else{
					if(msg && msg.otherData && msg.otherData.type){
						if(msg.otherData.type == 'iconMenus'){
						    datadesc = `[表情]`;
							if(msg.otherData.typedata && msg.otherData.typedata.name){
								datadesc += `[${msg.otherData.typedata.name}]`;
							}
						}else if(msg.otherData.type == 'image'){
							datadesc = `[图片]`;
						}
					}
				}
			    break;
			case 'audio':
			    datadesc = `[语音]`;
			    break;
			case 'video':
			    datadesc = `[视频]`;
			    break;
			case 'file':
			    datadesc = `[文件]`;
			    break;
			case 'pdf':
			    datadesc = `[pdf文件]`;
			    break;
			case 'docx':
			    datadesc = `[word文档]`;
			    break;
		}
		// 是否显示发送者
		datadesc = isSend ? datadesc : `${msg.from_name}: ${datadesc}`;
		console.log('消息页显示[图片][视频]等的显示数据',datadesc);
		return datadesc;
	}
	
	
	// 更新(获取)消息页,整个消息列表的未读数(不急可以异步执行)
	async updateXiaoXiListNoreadNum(list = false) {
		// 获取消息页列表本地历史信息
		list = !list ? this.getXiaoXiList() : list;
		// 循环list里面的每一项把属性noreadnum相加一起
		let num = 0;
		list.forEach(v => {
			num += v.noreadnum;
		});
		// 可在这里执行更新,或者赋值给实例属性在页面调用
		// 实例属性:消息页整个聊天列表数据的未读数
		this.xiaoxiNoreadNum = num;
		console.log('消息页整个聊天列表数据的未读数:', num);
		// 消息总未读数变化触发
		uni.$emit('totalNoReadNum', num);
		// 还可以返回
		return num;
	}
	

	// 数组元素置顶
	arrToFirst(arr, index) {
		// 判断:因为等于0本来就在最上面
		if (index != 0) {
			arr.unshift(arr.splice(index, 1)[0]);
		}
		return arr;
	}
	


	// 获取消息页列表本地历史信息
	getXiaoXiList() {
		// 定义消息列表key,支持多用户切换
		let key = 'chatlist_' + this.user.id;
		let list = uni.getStorageSync(key);
		return list ? JSON.parse(list) : [];
	}

	// 存储消息页列表本地历史信息
	setXiaoXiList(list) {
		// 定义消息列表key,支持多用户切换
		let key = 'chatlist_' + this.user.id;
		uni.setStorageSync(key, JSON.stringify(list));
	}

	// 查看我是否在线websocket是否正常连接
	meIsOnline() {
		if (!this.isOnline) {
			// 我不在线可以提示我确认重新连接websocket
			this.connectWebsocketcomfirm();
			return false;
		}
		return true;
	}


	// 提示我确认重新连接websocket
	connectWebsocketcomfirm() {
		uni.showModal({
			title: '系统提示',
			content: '由于服务器或者网络原因,您已经掉线了,是否重新连接',
			showCancel: true,
			cancelText: '取消',
			confirmText: '重新连接',
			success: res => {
				if (res.confirm) {
					this.connectSocket();
				}
			},
		});
	}
	

}

export default chatClass;

# 七、消息页整个聊天列表数据获取及展示

# 1. 消息页整个聊天列表数据获取

我们可以在vuex中定义一个 消息页整个聊天列表数据获取的方法,然后在 初始化登录注册状态initChatuserAction注册登录后续的操作regloginAction 中执行。
在文件 /store/modules/chatuser.js

...
export default {
	// 对应的mapState,在computed中引用导入
	// 类似于data,把全局或者公共部分放在这里
	state: {
		...
		// 聊天类
		chatClass:null,
		//消息页整个聊天列表数据
		xiaoxiList:[],
	},
	// 异步的方法,在methods引入
	actions: {
		//注册登录后续的操作
		regloginAction({commit,state,dispatch}, regloginRes) {
			...
			//连接websocket服务
			//dispatch('connectWebsocket',state.regloginUser);
			state.chatClass = new chatClass();
			//消息页整个聊天列表数据获取
			dispatch('getXiaoXiDataList');
		},
		// 用户退出登录
		...
		// 初始化登录注册状态(避免刷新页面state没有数据)
		initChatuserAction({commit,state,dispatch}) {
			console.log('初始化登录注册状态', state);
			// 给state赋值
			let user = uni.getStorageSync('chatuser') ?
				JSON.parse(uni.getStorageSync('chatuser')) : '';
			if (user) {
				...
				//连接websocket服务
				//dispatch('connectWebsocket',user);
				state.chatClass = new chatClass();
				//消息页整个聊天列表数据获取 
				dispatch('getXiaoXiDataList');
			}
		},
		//获取好友申请的信息
		...
		// 获取好友列表
		...
		//连接websocket服务(可以删除了,因为已经写在类里面了)
		...
		//消息页整个聊天列表数据获取
		getXiaoXiDataList({commit,state,dispatch}){
			// 聊天类里面有这个方法
			state.xiaoxiList = state.chatClass.getXiaoXiList();
			// 监听会话列表更新 在类`updateXiaoXiList`方法有触发事件'updateXiaoXiList'
			// 一旦有新消息,整个列表更新一下, res就是整个列表更新的结果
			uni.$on('updateXiaoXiList', res => {
				console.log('触发消息页列表数据更新', res);
				state.xiaoxiList = res;
			});
		},
	},
}

# 2. 在消息页读取数据并展示

在页面 /pages/xiaoxi/xiaoxi.nvue

<template>
	<view>
		...
	</view>
</template>

<script>
	import parseTimeJs from '@/common/mixins/parseTime.js';
	import {requestUrl} from '@/common/mixins/configData.js';
	import {mapState,mapGetters,mapMutations,mapActions} from 'vuex';
	export default {
		mixins:[parseTimeJs],
		data() {
			return {
				...
			}
		},
		onLoad() {
			this.getDataList();// 获取消息列表
		},
		onShow() {
			this.getDataList();// 获取消息列表
		},
		computed: {
			...mapState({
				me : state => state.Chatuser.regloginUser,
				xiaoxiList : state => state.Chatuser.xiaoxiList,
			}),
			...
		},
		methods: {
			// 获取消息列表
			getDataList(){
				console.log('获取消息列表',this.xiaoxiList);
				this.chatList = this.xiaoxiList.map(v=>{
					return {
						...v,
						avatar: v.avatar.startsWith('http') ? v.avatar : 
						requestUrl.http + v.avatar,
						nickname: v.name,
						chat_time: parseTimeJs.gettime(v.update_time),
						data: v.data.data,
						datacount: v.noreadnum,
						isZhiding: v.istop,
					}
				});
			},
			...,
			openChat(e) {
				console.log('进入聊天详情页', e);
				// 进入聊天页 传一下用户的信息过去在页面展示
				let userchat = {
					id: e.item.id,
					name: e.item.name,
					avatar: e.item.avatar,
					chatType: e.item.chatType, // 单聊 single 群聊 group
				};
				uni.navigateTo({
					url: `/pages/chat/chat?arg=${encodeURIComponent(JSON.stringify(userchat))}`,
				});
			},
			...,
		}
	}
</script>


发现几个问题:

  1. 从用户列表点击用户然后发消息,发现 如果是我的好友,但是我没有备注他,他自己设置了昵称,进入用户详情,显示的是用户账号(比如:彦祖这个用户)
  2. 消息列表页的用户称呼也存在这个问题;

# 1. 先修正用户详情页的称呼

在页面 /pages/userinfo/userinfo.vue

// 是不是我的好友
ismygoodfriend(){
	uni.$u.http.post(requestUrl.http + `/api/chat/ismygoodfriend/${this.user.id}`,{},{
		...
	}).then(res => {
		...
		if(res.data.msg == 'ok'){
			this.me.ismygoodfriend = res.data.data;
			this.user.nickname = res.data.data.nickname || this.user.nickname || this.user.username;
			uni.setNavigationBarTitle({
			    title: this.user.nickname,
			});
		}
	}).catch(err => {
		...
	});
},

# 八、聊天页接收消息及展示

# 1. 处理接收到的消息

在类文件 /common/js/chatClass.js

...
class chatClass {
	// 构造函数
	constructor() {
		...
	}

	// 连接websocket
	...

	// 连接成功
	...

	// 断开连接
	...

	// 接收信息
	...

	// 关闭链接
	...

	// 创建聊天对象信息
	...

	// 销毁聊天对象信息
	...

	// 页面发消息的格式和服务器要一致
	...

	// 发送消息(单聊)
	...

	// 把聊天信息存在本地
	...
	// 获取历史记录, 传key值则找指定聊天记录
	...

	// 更新指定的历史记录信息(不急着更新可以异步)
	...

	// 消息页的聊天列表更新一下
	...
	
	// 更新(获取)消息页,整个消息列表的未读数(不急可以异步执行)
	...

	// 数组元素置顶
	...

	// 获取消息页列表本地历史信息
	...

	// 存储消息页列表本地历史信息
	...

	// 查看我是否在线websocket是否正常连接
	...

	// 提示我确认重新连接websocket
	...

    // 处理接收到的消息
    async doMessage(msg){
		console.log('处理接收到的消息',msg);
		if(msg.type == 'system'){
			console.log('系统消息单独处理');
		}else if(msg.type == 'singleChat'){
			let res = msg.data;
			// 把聊天信息存在本地
			let { data } = this.addChatInfo(res, false);
			// 消息页的聊天列表更新一下
			this.updateXiaoXiList(data, false);
			// 全局通知数据
			uni.$emit('onMessage', data);
			
		}
	}
	
	// 进入聊天页,将消息页当前聊天用户的未读数清零
	async goChatPageUpdateXiaoXiNoReadNum(to_id, chatType){
		let xiaoxiList = this.getXiaoXiList();
		// 找到当前聊天
		let index = xiaoxiList.findIndex(v => v.id == to_id && v.chatType == chatType);
		if(index != -1){
			// 找到了
			xiaoxiList[index].noreadnum = 0;
			// 修改完了之后,存储消息页列表本地历史信息
			this.setXiaoXiList(xiaoxiList);
			// 更新(获取)消息页,整个消息列表的未读数(不急可以异步执行)
			this.updateXiaoXiListNoreadNum();
			// 消息页,整个消息列表的数据也存入了vuex中
			// 更新一下vuex中的消息列表的数据
			uni.$emit('updateXiaoXiList', xiaoxiList);
			
		}
	}

}

export default chatClass;

# 2. 进入聊天页,将消息页当前聊天用户的未读数清零

在文件 /store/modules/chatuser.js

import {requestUrl} from '@/common/mixins/configData.js';
import chatClass from '@/common/js/chatClass.js';
export default {
	// 对应的mapState,在computed中引用导入
	// 类似于data,把全局或者公共部分放在这里
	state: {
		...
		// 消息总的未读数
		totalNoReadNum: 0,
	},
	// 异步的方法,在methods引入
	actions: {
		//注册登录后续的操作
		regloginAction({commit,state,dispatch}, regloginRes) {
			...
			//连接websocket服务
			//dispatch('connectWebsocket',state.regloginUser);
			state.chatClass = new chatClass();
			//消息页整个聊天列表数据获取
			dispatch('getXiaoXiDataList');
			//初始化消息总未读数
			dispatch('getXiaoXiNoReadNum');
		},
		// 用户退出登录
		logoutAction({commit,state}) {
			...
		},
		// 初始化登录注册状态(避免刷新页面state没有数据)
		initChatuserAction({commit,state,dispatch}) {
			...
			if (user) {
				...
				//连接websocket服务
				//dispatch('connectWebsocket',user);
				state.chatClass = new chatClass();
				//消息页整个聊天列表数据获取 
				dispatch('getXiaoXiDataList');
				//初始化消息总未读数
				dispatch('getXiaoXiNoReadNum');
			}
		},
		//获取好友申请的信息
		...
		// 获取好友列表
		...
		//连接websocket服务(可以删除了,因为已经写在类里面了)
		...
		//消息页整个聊天列表数据获取
		...
		//初始化消息总未读数
		getXiaoXiNoReadNum({commit,state,dispatch}){
			console.log('消息未读数调用类实例属性', state.chatClass.xiaoxiNoreadNum);
			console.log('消息未读数调用类方法获取promise', state.chatClass.updateXiaoXiListNoreadNum());
			state.totalNoReadNum = state.chatClass.xiaoxiNoreadNum;
			// 监听消息总未读数变化
			uni.$on('totalNoReadNum', num => {
				state.totalNoReadNum = num;
				console.log('监听消息总未读数变化', num);
			});
		},
	},
}

# 3. 消息页监听接收消息(处理未读数等变化)

在页面 /pages/xiaoxi/xiaoxi.nvue

<template>
	<view>
		<!-- 导航栏 -->
		<chat-navbar :title="`消息(${totalNoReadNum})`" ...></chat-navbar>

		<!-- 消息列表 -->
		...

		<!-- 弹出菜单 -->
		...

	</view>
</template>

<script>
	...
	export default {
		...,
		data() {
			return {
				...
			}
		},
		onLoad() {
            this.getDataList();
			// 监听接收消息
		    uni.$on('onMessage', res => {
			    this.getDataList();
		    });
		},
		onShow() {
		   ...
		},
		computed: {
			...mapState({
				...,
				totalNoReadNum: state => state.Chatuser.totalNoReadNum,
				chatClass: state => state.Chatuser.chatClass,
			}),
			...
		},
		methods: {
			// 获取消息列表
			getDataList(){
				console.log('消息总未读数', this.totalNoReadNum);
				console.log('消息列表', this.xiaoxiList);
				...
			},
			openChat(e) {
				console.log('进入聊天详情页', e);
				// 更新一下未读消息数量
				this.chatClass.goChatPageUpdateXiaoXiNoReadNum(e.item.id,e.item.chatType);
				// 进入聊天页 传一下用户的信息过去在页面展示
				...
			},
			...
		}
	}
</script>

# 4. 聊天页头像处理

在文件 /pages/chat/plusIconAction.js

//发送消息
sendMessage(msgType, option = {}){
	...
	// 组织一下消息格式和服务器一致
	...
	// 显示到页面上
	msg.avatar = serverMsg.from_avatar.startsWith('http') ? serverMsg.from_avatar : 
					requestUrl.http + serverMsg.from_avatar;
	...
	...
	// 发给服务器消息
	this.chatClass.sendmessage(serverMsg).then(result => {
		...
		// 拿到刚发送的消息赋值
		this.chatDataList[sendmsgIndex].id = result.data.data.id;
		...
	}).catch(error => {
		...
	});
	...

},

# 5. 聊天页接收消息及展示

在页面 /pages/chat/chat.nvue

<template>
	<view>
		...
		 
	</view>
</template>

<script>
    import toolJs from '@/common/mixins/tool.js';
	import {requestUrl} from '@/common/mixins/configData.js';
	...
	export default {
		...,
		onLoad(e) {
			...
			try{
				...
				// 创建聊天对象信息
				...
				// 标题
				uni.setNavigationBarTitle({
					title: this.arg.name,
				});
				// 获取历史记录, 传key值则找指定聊天记录
				let chatdata = this.chatClass.getChatInfo();
				console.log('聊天页聊天历史', chatdata);
				if(chatdata.length){
					this.chatDataList = chatdata.map(v => {
						/*
						let evedata = typeof v.data == 'string' ? 
						JSON.parse(v.data) : v.data;
						return {
							avatar: v.from_avatar.startsWith('http') ? v.from_avatar : 
							        requestUrl.http + v.from_avatar,
							nickname: v.name,
							chat_time: v.create_time,
							data: evedata.data,
							user_id: v.from_id,
							type: v.type, //image,video
							isremove:false,
						}
						*/
					    return this.formatServerMsg(v);
					});
				}else{
					// 清空模拟数据
					this.chatDataList = [];
				}
				// 监听接收消息
				uni.$on('onMessage', v =>{
					console.log('监听接收消息', v);
					if(v.from_id == this.arg.id && v.chatType == this.arg.chatType){
						this.chatDataList.push(this.formatServerMsg(v));
					}
				});
			}catch{
				...
			}
		},
		destroyed() {
			//销毁聊天对象信息
			...
			//销毁监听接收消息 注意后面要传回调函数 
			//因为页面没有销毁只是被隐藏了 不然下次无法监听
			uni.$off('onMessage', ()=>{});
		},
		computed:{
			...mapState({
				recorderManager:state=>state.Audio.recorderManager,
				recorderDuration:state=>state.Audio.recorderDuration,
				chatClass:state=>state.Chatuser.chatClass,
				me: state=>state.Chatuser.regloginUser,
			}),
			...
		},
		methods: {
			...mapMutations(['regSendMessage']),
			// 接收的消息格式化渲染
			formatServerMsg(v){
				let evedata = typeof v.data == 'string' ?
				JSON.parse(v.data) : v.data;
				return {
					avatar: v.from_avatar.startsWith('http') ? v.from_avatar : 
					        requestUrl.http + v.from_avatar,
					nickname: v.name,
					chat_time: v.create_time,
					data: evedata.data,
					user_id: v.from_id,
					type: v.type, //image,video
					isremove:false,
				}
			},
			...
		},
		...
	}
</script>

# 九、聊天页更多内容

由于内容较多,在新页面查看: 聊天页更多内容

更新时间: 2025年9月4日星期四晚上7点59分