文章概述
整个IM项目的关键点来了,本文将讨论一下聊天消息的实现,如何收发消息并且实现聊天消息的UI显示。
聊天消息项的实现
1.收发聊天消息
1.1 接收聊天消息
接收聊天消息显得很简单,在之前的会话列表实现中已经做过一次,这次我们代码其实差不多,唯一不一样的是接受后我们需要做筛选确定需要回显到聊天消息界面的项目。
绑定消息事件部分的代码如下:
// 这里选择在onLoad绑定,确定不会漏接数据
async onLoad (params) {
// 做好一个变量确定我现在在和谁聊天
this.receiver = params.receiver
// 监听新的消息
this.$txim.$on('onRecvC2CTextMessage', this.onRecvMessageHanlder)
this.$txim.$on('onRecvC2CCustomMessage', this.onRecvMessageHanlder)
this.$txim.$on('onRecvGroupTextMessage', this.onRecvMessageHanlder)
this.$txim.$on('onRecvGroupCustomMessage', this.onRecvMessageHanlder)
this.$txim.$on('onRecvNewMessage', this.onRecvMessageHanlder)
}
接收到消息的回调事件处理如下
// 获取消息
async onRecvMessageHanlder ({ data }) {
let V2TIMMessageManager = this.$txim.getMessageManager()
let senderId = data?.sender?.userID || data?.sender
if (senderId == this.receiver) {
this.HistoryMessageToChatLog([data])
this.$txim.markC2CMessageAsRead(senderId)
await this.$nextTick()
this.$refs.chatLayout.scrollToBottom()
}
},
1.2 发送聊天消息
发送聊天消息实际上demo已经有所封装了,对于文本消息而言发送的代码如下:
// 发送文字 这里需要防抖处理,反正多次点击发送按钮从而出现问题
sendText: _.debounce(async function (text) {
let V2TIMMessageManager = this.$txim.getMessageManager()
// 创建消息结构体
let v2TIMMessage = V2TIMMessageManager.createTextMessage(text)
try {
// 发送消息
let ret = await V2TIMMessageManager.sendMessage(v2TIMMessage, this.receiver)
// 更新聊天消息
this.HistoryMessageToChatLog([ret.data])
} catch (e) {
this.$utils.toast('发送失败')
}
await this.$nextTick()
// 保持滚动到底部
this.$refs.chatLayout.scrollToBottom()
}, 500, { leading: true, trailing: false }),
上面演示的是文本消息,而对于其他消息而言,只是创建的消息结构体不一样,各种消息的结构体如下
// 文本
let v2TIMMessage = V2TIMMessageManager.createTextMessage(text)
// 大表情
let v2TIMMessage = V2TIMMessageManager.createFaceMessage(0, { faceUrl: url })
// 图片
let v2TIMMessage = V2TIMMessageManager.createImageMessage(filePath)
// 视频
let v2TIMMessage = V2TIMMessageManager.createVideoMessage(filePath, 'mp4', timeLen, snapshotPath)
// 语音
let v2TIMMessage = V2TIMMessageManager.createSoundMessage(filePath, timeLen)
// 定位
let v2TIMMessage = V2TIMMessageManager.createLocationMessage(address, longitude, latitude)
2.聊天消息格式化
为了做好组件之间的解耦合,我们需要将腾讯消息的结构体进行格式化,映射为我们消息组件中能识别的结构体,映射部分的代码实际上utils/imUtils文件中已经有所提供了,具体的操作如下(这里需要注意,里面根据了消息记录的elemType做了判断,elemType腾讯TXIM的官方文档对应这里https://im.sdk.qcloud.com/doc/zh-cn/classcom_1_1tencent_1_1imsdk_1_1v2_1_1V2TIMMessageManager.html)
async HistoryMessageToChatLog (msgLogs, unshift) {
for (let item of msgLogs) {
let msgType = [
null,
this.$imUtils.MSG_TEXT,
null,
this.$imUtils.MSG_IMAGE,
this.$imUtils.MSG_AUDIO,
this.$imUtils.MSG_VIDEO,
null,
this.$imUtils.MSG_LOCATION,
this.$imUtils.MSG_TIP_FACE,
][item.elemType]
let dataJson = {
id: item.msgID,
head: '../static/icon_u_head.jpg',
self: item.isSelf,
type: msgType,
data: null
}
switch (msgType) {
case this.$imUtils.MSG_TEXT:
dataJson.data = this.$imUtils.buildMessageItem(msgType, item.elem.text, item)
break
case this.$imUtils.MSG_LOCATION:
dataJson.data = this.$imUtils.buildMessageItem(msgType, item, item.elem.latitude, item.elem.longitude, item.elem.desc, item)
break
case this.$imUtils.MSG_IMAGE:
dataJson.data = this.$imUtils.buildMessageItem(msgType, item.elem.imageList[0].url, item)
break
case this.$imUtils.MSG_AUDIO:
dataJson.data = this.$imUtils.buildMessageItem(msgType, '', item.elem.duration * 1000, item)
break
case this.$imUtils.MSG_VIDEO:
dataJson.data = this.$imUtils.buildMessageItem(msgType, item, item)
break
case this.$imUtils.MSG_TIP_FACE:
dataJson.data = this.$imUtils.buildMessageItem(msgType, item, item)
break
default:
// console.log(item)
}
if (msgType) {
this.chatLogs[unshift ? 'unshift' : 'push'](dataJson)
}
}
}
3.聊天消息回显
聊天消息部分demo已经内置了一个聊天组件,我们可以实现开箱即用,只需要引入组件定义好list即可
首先我们需要引入组件,并且在template对应的位置写好即可
<chat-message-item
v-for="item in chatLogs"
:ref="item.id"
:self="item.self"
:head="item.head"
:type="item.type"
:data="item.data"
:prevent="currentPopItem && currentPopItem.item.id == item.id"
@longpressContent="currentPopItem = { ref: $refs[item.id][0], item }"
>
</chat-message-item>
<script>
import ChatMessageItem from '../components/ChatMessageItem'
export default {
components: { ChatMessageItem },
}
</script>
4.聊天消息弹出菜单
一般而言我们聊天消息还需要有一些操作,比如删除,转发之类的,那么我们也需要为聊天消息增加弹出菜单,这需要我们去做浮动位置的计算,然而demo中已经提供了一个弹出菜单,使得我们可以很简单的实现这个功能。
聊天消息的弹出菜单组件在componetns/ChatMsgItemProp下,开发者需要自己修改成为自己需要的功能,这里我们先引入组件然后加入到template中对应位置
<chat-layout
class="page"
ref="chatLayout"
:end="end"
@upperLoading="loadMoreLog"
@scroll="onChatLayoutScroll"
@clickRoot="onRootClick"
>
<chat-message-item
v-for="item in chatLogs"
:ref="item.id"
:self="item.self"
:head="item.head"
:type="item.type"
:data="item.data"
:prevent="currentPopItem && currentPopItem.item.id == item.id"
@longpressContent="currentPopItem = { ref: $refs[item.id][0], item }"
>
</chat-message-item>
<!-- 这里要明显注意,弹出不是在ChatMessageItem里面的! -->
<chat-msg-item-pop
:show="currentPopItem"
@clickFn="onPopClickFn($event)"
></chat-msg-item-pop>
</chat-layout>
这里我们主要是从chatMessageItem中获取到了长按事件的坐标信息,然后交给chatMsgItemPop,弹出组件会根据坐标计算确定显示位置和方向,计算部分的算法如下:
calcPopPosition () {
let { ref } = this.show
let el = ref.$el
let pop = this.$refs.pop
let content = el.children[2]
dom.getComponentRect(pop, ({ size }) => {
let popSize = size
dom.getComponentRect(content, ({ size }) => {
let contentSize = size
this.popPoint.reverse = (contentSize.top - popSize.height) < 0
this.popPoint.popHeight = popSize.height
this.popPoint.itemHeight = contentSize.height
this.popPoint.touchX = contentSize.left + contentSize.width / 2
this.popPoint.touchY = contentSize.top
})
})
}
通过计算确定了显示位置之后就会显示功能菜单列表,在组件中内置了以下的按钮图标,功能暂未实现,开发者可以自己根据实际需要定制化处理哦。
{ type: 'copy', label: '复制', icon: '/static/icon_copy.png' },
{ type: 'share', label: '转发', icon: '/static/icon_share.png' },
{ type: 'collect', label: '收藏', icon: '/static/icon_collect.png' },
{ type: 'delete', label: '删除', icon: '/static/icon_delete.png' },
{ type: 'ref', label: '引用', icon: '/static/icon_ref_msg.png' },
{ type: 'translate', label: '翻译', icon: '/static/icon_fanyi.png' },
{ type: 'search', label: '搜一搜', icon: '/static/icon_search_fun.png' },
项目开源地址及交流群
项目成品效果查看:请点击项目引言
项目开源地址:https://gitee.com/ckong/Zhimi...
Uniapp开发交流群:755910061