学习目标
项目中集成 IM 即时通讯
利用环信 IM Web SDK 快速实现在 Vue.js 中发送出一条 Hello World!
前置技能
Node.js 环境已搭建。
npm 包管理工具的基本使用。
Vue2 或者 Vue3 框架基本掌握或使用。
一、了解环信 IM
二、环信 WebIM 实现通讯的基本流程
前置准备
我们开始
初期配置
在确保已进行 npm install easemob-websdk 安装了环信 SDK 包,并已经下载了 Vue3 官方 Demo,将项目中的 IM 文件拖入自己的项目中。
此文件共两个功能:
引入环信 WebIM-SDK
将引入的 SDK 进行实例化
将DEFAULT_APPKEY修改为自己已注册的 Appkey。
配置监听
<script setup> import { EaseChatClient } from '@/IM/initwebsdk' /* SDK连接 相关监听 */ EaseChatClient.addEventHandler('connection', { onConnected: () => {}, //与环信服务器建联成功回调。 onDisconnected: () => {}, //与环信服务器断开成功回调。 onOnline: () => {}, // 本机网络连接成功。 onOffline: () => {},// 本机网络掉线。 onError: (error) => {}, //SDK Error 回调 }) /* 好友关系相关监听 */ EaseChatClient.addEventHandler('friendListen', { // 收到好友邀请触发此方法。 onContactInvited: (data) => {}, // 联系人被删除时触发此方法。 onContactDeleted: (data) => {}, // 新增联系人会触发此方法。 onContactAdded: (data) => {}, // 好友请求被拒绝时触发此方法。 onContactRefuse: (data) => {}, // 好友请求被同意时触发此方法。 onContactAgreed: (data) => {} }) /* message 相关监听 */ EaseChatClient.addEventHandler('messageListen', { onTextMessage: function (message) {}, // 收到文本消息。 onEmojiMessage: function (message) {}, // 收到表情消息。 onImageMessage: function (message) {}, // 收到图片消息。 onCmdMessage: function (message) {}, // 收到命令消息。 onAudioMessage: function (message) {}, // 收到音频消息。 onLocationMessage: function (message) {}, // 收到位置消息。 onFileMessage: function (message) {}, // 收到文件消息。 onCustomMessage: function (message) {}, // 收到自定义消息。 onVideoMessage: function (message) {}, // 收到视频消息。 onRecallMessage: function (message) {}, // 收到消息撤回回执。 }) </script>
创建测试 ID
登录环信
这一步是所有后续操作的第一步
<script setup> import { EaseChatClient } from '@/IM/initwebsdk' const loginValue = reactive({ user: '', //你的测试环信ID password: '' //你的测试环信ID密码 }) //登录接口调用 const loginIM = async () => { try { await EaseChatClient.open({ user: loginValue.username.toLowerCase(), pwd: loginValue.password.toLowerCase() } ); } catch (error) { console.log('>>>>登录失败', error); } } </script>
紧接着是开始聊天部分。
好友关系
完成这个功能 需要将该项目开启两个页面,一个申请,一个接收,这样才能看到效果
两种方式:手动关联一个好友,第二种再创建一个测试 ID 之后,调用 SDK 添加好友。
方式一:测试时最简单的方式,手动关联好友
在管理后台中手动再创建一个 ID:image.png
并手动将新创建的 ID 关联为好友。
方式二:开发时调用 SDK 接口添加好友
//申请添加好友 const applyAddFriends = () => { EaseChatClient.addContact(targetId, '我想加你为好友!'); }; //接收方登录将会触发 EaseChatClient.addEventHandler('friendListen', { // 收到好友邀请触发此方法。 onContactInvited: (data) => { //同意申请 EaseChatClient.acceptContactInvite(data.from); //拒绝申请 EaseChatClient.declineContactInvite(data.from); }, });
进入页面获取好友列表并自行渲染。
<script setup> //获取好友列表 const friendListData = reactive({}) const { data } = await EaseChatClient.getContacts() data.length > 0 && data.map(item => (friendListData[item] = { hxId: item })) </script>
收发消息
完成这个功能 需要将该项目开启两个页面,一个发送,一个接收,这样才能看到效果
发送方发送一条文本消息:
<script setup> const props = defineProps({ nowPickInfo: { type: Object, required: true, default: () => ({}) } }) const { nowPickInfo } = toRefs(props) const { ALL_MESSAGE_TYPE, CHAT_TYPE } = messageType //发送文本内容 const textContent = ref('') const sendTextMessage = _.debounce(async () => { //如果输入框全部为空格同样拒绝发送 if (textContent.value.match(/^\s*$/)) return const msgOptions = { id: nowPickInfo.value.id, //要发送的目标ID chatType: nowPickInfo.value.chatType, msg: textContent.value, } textContent.value = '' //发送后清空输入框 try { await store.dispatch('sendShowTypeMessage', { msgType: ALL_MESSAGE_TYPE.TEXT, msgOptions }) } catch (error) { console.log('>>>>>>>发送失败+++++++', error) } }, 50) </script>
接收方接收消息
/* message 相关监听 */ EaseChatClient.addEventHandler('messageListen', { onTextMessage: function (message) { console.log('>>>>收到文本消息'); pushNewMessage(message); //在缓存中Push一条新消息。 }, // 收到文本消息。 });
缓存的消息结构示例
messageList:{ //以好友的ID为KEY,如果获取则直接messageList[friendId]取到对应的消息。 friendId:[ { chatType:"singleChat", //聊天类型 单聊或者群聊 ext:{}, //消息扩展 from:friendId, //消息来源ID id:"1111864344594875684", //消息的唯一ID msg:"Hello World!",//消息内容 time:1676440891009,//消息发送时间 to:myId,//发送目标ID type:"txt" //消息来源 }, { chatType:"singleChat", ext:{}, from:friendId, id:"1111864344594875684", msg:"Hello World2!", time:1676440891009, to:myId, type:"txt" } ], friendId2:[ { chatType:"singleChat", ext:{}, from:friendId, id:"1111864344594875684", msg:"Hello World!", time:1676440891009, to:myId, type:"txt" }, ] }
渲染消息列表
<script setup> import { reactive, ref, computed, toRefs } from 'vue' //获取其id对应的消息内容 const messageData = computed(() => { //如果Message.messageList中不存在的话调用拉取漫游取一下历史消息 return nowPickInfo.value.id && store.state.Message.messageList[nowPickInfo.value.id] || fechHistoryMessage('fistLoad')() }) <template> <div> <div class="messageList_box" v-for="(msgBody, index) in messageData" :key="msgBody.id"> <div v-if="!msgBody.isRecall && msgBody.type !== ALL_MESSAGE_TYPE.INFORM" class="message_box_item" :style="{ flexDirection: (isMyself(msgBody) ? 'row-reverse' : 'row') }"> <div class="message_item_time">{{ handleMsgTimeShow(msgBody.time, index) || '' }}</div> <el-avatar class="message_item_avator" :src="isMyself(msgBody) ? loginUserInfo.avatarurl : otherUserInfo(msgBody.from).avatarurl || defaultAvatar"> </el-avatar> <el-dropdown class="message_box_content" :class="[isMyself(msgBody) ? 'message_box_content_mine' : 'message_box_content_other']" trigger="contextmenu" placement="bottom-end"> <!-- 文本类型消息 --> <p style="padding: 10px" v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT"> {{ msgBody.msg }} </p> <!-- 图片类型消息 --> <!-- <div> --> <el-image v-if="msgBody.type === ALL_MESSAGE_TYPE.IMAGE" style="border-radius:5px;" :src="msgBody.thumb" :preview-src-list="[msgBody.url]" :initial-index="1" fit="cover" /> <!-- </div> --> <!-- 语音类型消息 --> <div :class="['message_box_content_audio', isMyself(msgBody) ? 'message_box_content_audio_mine' : 'message_box_content_audio_other']" v-if="msgBody.type === ALL_MESSAGE_TYPE.AUDIO" @click="startplayAudio(msgBody, index)" :style="`width:${msgBody.length * 10}px`"> <span class="audio_length_text"> {{ msgBody.length }}′′ </span> <div :class="[isMyself(msgBody) ? 'play_audio_icon_mine' : 'play_audio_icon_other', audioPlayStatus.playIndex === index && 'start_play_audio']" style=" background-size: 100% 100%;"> </div> </div> <div v-if="msgBody.type === ALL_MESSAGE_TYPE.LOCAL"> <p style="padding: 10px">[暂不支持位置消息展示]</p> </div> <!-- 文件类型消息 --> <div v-if="msgBody.type === ALL_MESSAGE_TYPE.FILE" class="message_box_content_file"> <div class="file_text_box"> <div class="file_name">{{ msgBody.filename }}</div> <div class="file_size">{{ fileSizeFormat(msgBody.file_length) }}</div> <a class="file_download" :href="msgBody.url" download>点击下载</a> </div> <span class="iconfont icon-wenjian"></span> </div> <!-- 自定义类型消息 --> <div v-if="msgBody.type === ALL_MESSAGE_TYPE.CUSTOM" class="message_box_content_custom"> <template v-if="msgBody.customEvent && CUSTOM_TYPE[msgBody.customEvent]"> <div class="user_card"> <div class="user_card_main"> <!-- 头像 --> <el-avatar shape="circle" :size="50" :src="msgBody.customExts && msgBody.customExts.avatarurl || msgBody.customExts.avatar || defaultAvatar" fit="cover" /> <!-- 昵称 --> <span class="nickname">{{ msgBody.customExts && msgBody.customExts.nickname || msgBody.customExts.uid }}</span> </div> <el-divider style="margin:5px 0; border-top:1px solid black;" /> <p style="font-size: 8px;">个人名片</p> </div> </template> </div> <template #dropdown> <el-dropdown-menu> <el-dropdown-item v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT && isSupported" @click="copyTextMessages(msgBody.msg)"> 复制 </el-dropdown-item> <el-dropdown-item v-if="isMyself(msgBody)" @click="recallMessage(msgBody)"> 撤回 </el-dropdown-item> <el-dropdown-item @click="deleteMessage(msgBody)"> 删除 </el-dropdown-item> <el-dropdown-item v-if="!isMyself(msgBody)" @click="informOnMessage(msgBody)"> 举报 </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> <div v-if="msgBody.isRecall" class="recall_style">{{ isMyself(msgBody) ? "你" : `${msgBody.from}` }}撤回了一条消息<span class="reEdit" v-show="isMyself(msgBody) && msgBody.type === ALL_MESSAGE_TYPE.TEXT" @click="reEdit(msgBody.msg)">重新编辑</span></div> <div v-if="msgBody.type === ALL_MESSAGE_TYPE.INFORM" class="inform_style"> <p> {{ msgBody.msg }} </p> </div> </div> <ReportMessage ref="reportMessage" /> </div> </template> </script>
了解即时通讯IM及应用场景请访问:环信官网:
https://www.easemob.com/
更多集成IM教程请访问:IMGeek社区
https://www.imgeek.net/