# 一、连接即时通讯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';
});
// 清空发送的内容然后还要滚动到底部
...
},
# 六、发消息前把消息添加到本地历史记录,消息页的聊天列表更新一下,发完之后更新一下本地历史记录
说明:
- 由于这部分内容比较抽象,我就不手敲了,它的实现流程其实相当于是固定的,因此我这里就直接给大家提供实现这些功能的方法;
- 方法里面每行代码我都提供了注释,大家可以根据注释进行代码的阅读;
- 大家可以根据注释说明打印相关的变量属性,增加理解;
- 大家跟着老师测试这些代码效果即可;
在文件 /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. 先修正用户详情页的称呼
在页面 /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>
# 九、聊天页更多内容
由于内容较多,在新页面查看: 聊天页更多内容