vue 集成环信im 简单demo
环信AppKey:1106190415055331#test
测试账号: test1 123456 test2 123456 test3 123456
默认登录test1
用vuex统一管理聊天对象、聊天信息等
点击相应的对象,发送文本
1. 引入环信依赖
2. vuex 环信前期准备全局状态控制
import {dotData} from '@/utils'
import Vue from 'vue'
// im聊天状态
const IM_CHART_DIALOG = {
state: {
visible: false, // 显示隐藏
rosterList: [], // 好友列表
groupList: [], // 群组列表
groupMessageMap: {}, // 聊天信息集合
groupMessageHistoryMap: {}, // 历史消息
currentGroupId: '', // 当前活动
extMap: {}, // 附加信息
eNameMap: {}, // 用户名信息
disabledGroupId: [],
groupIdMap: {}, // 群消息
readonlyGroupIdArr: [] // 只读的groupId
},
mutations: { // 修改状态
SET_IM_CHART_DIALOG_VISIBLE(state, {visible}) { // 弹窗可见/隐藏
state.visible = visible
},
SET_IM_CHART_ROSTERLIST(state, rosterList) {
state.rosterList = rosterList
},
SET_IM_CHART_GROUPLIST(state, groupList) {
state.groupList = groupList
},
PUSH_IM_CHART_DIALOG_GROUP_MESSAGE(state, {message, groupId}) { // 用户聊天信息
let chart = state.groupMessageMap[groupId] || []
chart.push(message)
Vue.set(state.groupMessageMap, groupId, chart)
},
UN_SHIFT_IM_CHART_DIALOG_GROUP_MESSAGE(state, {message, groupId}) { // 用户聊天信息
let chart = state.groupMessageMap[groupId] || []
chart.unshift(message)
Vue.set(state.groupMessageMap, groupId, chart)
},
REMOVE_IM_CHART_DIALOG_GROUP_MESSAGE(state, groupId) { // 移除一个群聊
let chart = state.groupMessageMap
delete chart[groupId]
state.groupMessageMap = chart
},
SET_IM_CHART_DIALOG_GROUP_ID(state, groupId) { // 设置当前用户组
state.currentGroupId = groupId
},
SET_IM_CHART_EXT_MAP(state, {groupId, ext}) { // 设置group 附加信息
if (!state.extMap[groupId]) {
Vue.set(state.extMap, groupId, ext)
}
},
REMOVE_IM_CHART_EXT_MAP(state, groupId) { // 移除一个群聊追加消息
let ext = state.extMap
delete ext[groupId]
state.extMap = ext
},
REMOVE_IM_CHART_GROUP_ID(state, groupId) {
let groupIdMap = state.groupIdMap
delete groupIdMap[groupId]
state.groupIdMap = groupIdMap
},
// eName 和用户进行映射
SET_IM_CHART_DIALOG_MEMBER_E_NAME(state, {eName, member}) {
Vue.set(state.eNameMap, eName, member)
},
// 使当前用户组消息已读
SET_IM_CHART_DIALOG_GROUP_ID_READ(state, groupId) {
if (state.visible) {
let chart = state.groupMessageMap[groupId] || []
chart.forEach(v => {
Vue.set(v, 'isRead', true)
})
Vue.set(state.groupMessageMap, groupId, chart)
}
},
// 禁用的groupId
PUSH_IM_CHART_DIALOG_DISABLE_GROUP_ID(state, groupId) {
state.disabledGroupId.push(groupId)
},
// 群组信息
SET_IM_CHART_DIALOG_GROUP_ID_INFO(state, {groupId, info}) {
if (groupId) {
Vue.set(state.groupIdMap, groupId, info)
}
},
// 清除群消息
CLEAR_IM_CHART_DIALOG_GROUP_ID_INFO(state) {
state.groupIdMap = {}
},
// 历史记录拉取 第一次拉取为真
SET_IM_GROUP_MESSAGE_HISTORY_FIRST(state, {groupId}) {
let obj = state.groupMessageHistoryMap[groupId] || {}
Vue.set(obj, 'first', true)
Vue.set(state.groupMessageHistoryMap, groupId, obj)
},
// 历史记录拉取 已经没有数据了
SET_IM_GROUP_MESSAGE_HISTORY_OVER(state, {groupId}) {
let obj = state.groupMessageHistoryMap[groupId] || {}
Vue.set(obj, 'over', true)
Vue.set(state.groupMessageHistoryMap, groupId, obj)
},
// 只读的消息列表
SET_IM_READ_ONLY_GROUP_ID(state, {groupId}) {
state.readonlyGroupIdArr.push(groupId)
}
},
actions: { // 调用方法(接收传进来的参数)
// 展示弹窗
SET_IM_CHART_DIALOG_VISIBLE({commit}, {visible}) { // 弹窗可见
commit('SET_IM_CHART_DIALOG_VISIBLE', {visible})
},
// 好友列表
SET_IM_CHART_ROSTERLIST({commit}, rosterList) {
commit('SET_IM_CHART_ROSTERLIST', rosterList)
},
// 群组列表
SET_IM_CHART_GROUPLIST({commit}, groupList) {
commit('SET_IM_CHART_GROUPLIST', groupList)
},
// 添加消息
PUSH_IM_CHART_DIALOG_GROUP_MESSAGE({commit}, message) { // 聊天信息组
const groupId = dotData(message, 'groupId')
const ext = dotData(message, 'ext')
delete message.ext
commit('PUSH_IM_CHART_DIALOG_GROUP_MESSAGE', {groupId, message})
commit('SET_IM_CHART_EXT_MAP', {groupId, ext}) // 设置附加消息
},
UN_SHIFT_IM_CHART_DIALOG_GROUP_MESSAGE({commit}, message) { // 聊天信息组
const groupId = dotData(message, 'groupId')
const ext = dotData(message, 'ext')
delete message.ext
commit('UN_SHIFT_IM_CHART_DIALOG_GROUP_MESSAGE', {groupId, message})
commit('SET_IM_CHART_EXT_MAP', {groupId, ext}) // 设置附加消息
},
REMOVE_IM_CHART_DIALOG_GROUP_ID({commit, getters}, groupId) {
commit('REMOVE_IM_CHART_DIALOG_GROUP_MESSAGE', groupId) // 移除消息
commit('REMOVE_IM_CHART_EXT_MAP', groupId) // 移除附加消息
// 移除一个群
commit('REMOVE_IM_CHART_GROUP_ID', groupId)
commit('PUSH_IM_CHART_DIALOG_DISABLE_GROUP_ID', groupId)
if (groupId === getters.GET_IM_CHART_DIALOG_GROUP_ID) {
commit('SET_IM_CHART_DIALOG_GROUP_ID', '')
}
},
// 添加追加信息
SET_IM_CHART_EXT_MAP({commit}, {groupId, ext}) {
commit('SET_IM_CHART_EXT_MAP', {groupId, ext})
},
// 设置当前groupId
SET_IM_CHART_DIALOG_GROUP_ID({commit}, groupId) {
commit('SET_IM_CHART_DIALOG_GROUP_ID', groupId)
commit('SET_IM_CHART_DIALOG_GROUP_ID_READ', groupId)
},
// 添加禁用的groupId
PUSH_IM_CHART_DIALOG_DISABLE_GROUP_ID({commit, getters}, groupId) {
if (groupId) {
commit('PUSH_IM_CHART_DIALOG_DISABLE_GROUP_ID', groupId)
if (groupId === getters.GET_IM_CHART_DIALOG_GROUP_ID) {
commit('SET_IM_CHART_DIALOG_GROUP_ID', '')
}
}
},
SET_IM_CHART_DIALOG_GROUP_ID_INFO({commit}, {groupId, info}) { // 设置群聊信息
commit('SET_IM_CHART_DIALOG_GROUP_ID_INFO', {groupId, info})
if (Array.isArray(info.members)) {
info.members.forEach(v => {
commit('SET_IM_CHART_DIALOG_MEMBER_E_NAME', {eName: v.userEasemobName, member: v})
})
}
},
CLEAR_IM_CHART_DIALOG_GROUP_ID_INFO({commit}) { // 清空群信息
commit('CLEAR_IM_CHART_DIALOG_GROUP_ID_INFO')
},
SET_IM_GROUP_MESSAGE_HISTORY_FIRST({commit}, {groupId}) { // 第一次访问历史消息
commit('SET_IM_GROUP_MESSAGE_HISTORY_FIRST', {groupId})
},
SET_IM_GROUP_MESSAGE_HISTORY_OVER({commit}, {groupId}) { // 已无历史消息
commit('SET_IM_GROUP_MESSAGE_HISTORY_OVER', {groupId})
},
SET_IM_READ_ONLY_GROUP_ID({commit}, {groupId}) { // 只读消息
commit('SET_IM_READ_ONLY_GROUP_ID', {groupId})
}
},
getters: { // 弹窗展示
GET_IM_CHART_DIALOG_VISIBLE: state => {
return state.visible
},
GET_IM_CHART_ROSTERLIST: state => {
return state.rosterList || []
},
GET_IM_CHART_GROUPLIST: state => {
return state.groupList || []
},
GET_IM_CHART_DIALOG_GROUP_ID: state => { // 获取当前groupId
return state.currentGroupId
},
GET_IM_CHART_EXT_MAP: (state) => (groupId) => { // 根据用户组获取
if (state.extMap[groupId]) {
return state.extMap[groupId]
}
},
GET_IM_CHART_DIALOG_GROUP_MESSAGE: (state) => (groupId) => { // 获取消息组
if (groupId) {
return state.groupMessageMap[groupId] || []
}
return []
},
GET_IM_CHART_DIALOG_MEMBER_E_NAME: (state) => (eName) => { // 根据环信name 获取用户信息
if (eName) {
return state.eNameMap[eName] || {}
}
return state.eNameMap
},
// 获取未读消息条数
GET_IM_CHART_DIALOG_UN_READ_LENGTH: (state, getters) => (groupId) => { // 根据环信name 获取用户信息
if (Array.isArray(groupId)) {
let temp = 0
groupId.forEach(id => {
temp += getters.GET_IM_CHART_DIALOG_GROUP_MESSAGE(groupId).filter(v => v.isRead === false).length
})
return temp
} else {
return getters.GET_IM_CHART_DIALOG_GROUP_MESSAGE(groupId).filter(v => v.isRead === false).length
}
},
// 全部未读消息
GET_IM_CHART_DIALOG_ALL_UN_READ_LENGTH: (state) => { // 根据环信name 获取用户信息
let temp = 0
for (let k in state.groupMessageMap) {
if (Array.isArray(state.groupMessageMap[k])) {
temp += state.groupMessageMap[k].filter(v => v.isRead === false).length
}
}
return temp
},
// 获取最后一条消息
GET_IM_CHART_DIALOG_LAST_MESSAGE: (state, getters) => (groupId) => {
let temp = ''
if (Array.isArray(groupId)) {
groupId.forEach(id => {
let charts = getters.GET_IM_CHART_DIALOG_GROUP_MESSAGE(id)
let lastChart = ''
if (charts.length) {
lastChart = charts[charts.length - 1]
}
if (temp) {
if (lastChart) {
if (temp.timeStr < lastChart.timeStr) {
temp = lastChart
}
}
} else {
if (lastChart) {
temp = lastChart
}
}
})
} else {
let charts = getters.GET_IM_CHART_DIALOG_GROUP_MESSAGE(groupId)
if (charts.length) {
temp = charts[charts.length - 1]
}
}
if (temp) {
switch (temp.fileType) {
case 'audio':
return '[语音]'
case 'image':
return '[图片]'
default :
return temp.sourceMsg
}
}
},
// 历史消息对象
GET_IM_GROUP_MESSAGE_HISTORY: (state) => (groupId) => {
return dotData(state.groupMessageHistoryMap, groupId) || {}
},
// 是只读消息
IS_IM_READ_ONLY_GROUP_ID: (state) => (groupId) => {
return state.readonlyGroupIdArr.indexOf(groupId) >= 0
}
}
} export default IM_CHART_DIALOG
3. 构建环信实例
import store from '@/store'
import {alert, dotData} from '@/utils'
import moment from 'moment' export const Easemob = class Easemob {
static instance = null
connection = null
config = {}
username = ''
WebIM = window.WebIM // 初始化
constructor() {
if (Easemob.instance) {
return Easemob.instance
}
this.config = window.WebIM.config
Easemob.instance = this
} // 获取当前时间
_getTimeString() {
return moment().format('YYYY-MM-DD HH:mm:ss')
} // 获取连接
getConnection() {
if (!this.connection) {
const Connection = window.WebIM.connection
this.connection = new Connection({
isMultiLoginSessions: this.config.isMultiLoginSessions,
https: typeof this.config.https === 'boolean' ? this.config.https : location.protocol === 'https:',
url: this.config.xmppURL,
isAutoLogin: true,
heartBeatWait: this.config.heartBeatWait,
autoReconnectNumMax: this.config.autoReconnectNumMax,
autoReconnectInterval: this.config.autoReconnectInterval,
apiUrl: this.config.apiURL
})
}
return this.connection
} // 发送时追加信息
appendMessage(message, className = 'in') {
message = this.createClassNameAndTime(message, className)
message = this._setExt(message)
return message
} // 添加时间和来源
createClassNameAndTime(message, className = 'in') {
message.className = className
message.timeStr = message.timeStr || this._getTimeString()
message.loginUserName = this.username
return message
} // 设置追加信息
_setExt(message) {
return message
} // 发送已阅回执
readMessage(message) {
let ImMessage = this.WebIM.message
const id = this.getConnection().getUniqueId()
let msg = new ImMessage('read', id)
msg.set({
id: message.id,
to: message.from
})
this.getConnection().send(msg.body)
} // 登陆
login(username, password) {
this.getConnection().open({
apiUrl: this.config.apiURL,
user: username,
pwd: password,
appKey: this.config.appkey
}) const that = this this.connection.listen({
onOpened(message) {
that.username = username // 获取好友
this.getRoster({
success: roster => {
store.dispatch('SET_IM_CHART_ROSTERLIST', roster)
},
error: e => {
console.log(e)
}
})
// 获取群组
this.getGroup({
success: group => {
store.dispatch('SET_IM_CHART_GROUPLIST', group.data)
},
error: e => {
console.log(e)
}
})
},
// 文本消息
onTextMessage(message) {
message = that.createClassNameAndTime(message)
// 处理表情
if (typeof message.sourceMsg === 'string') {
Object.keys(that.config.emoConfig.map).forEach(v => {
message.data = message.sourceMsg = message.sourceMsg.replace(new RegExp(v, 'g'), `<img src="${that.config.emoConfig.path + that.config.emoConfig.map[v]}" />`)
})
}
let type = dotData(message, 'type')
if (type === 'groupchat') { // 群组消息
message.groupId = message.to
if (store.getters.GET_IM_CHART_DIALOG_GROUP_ID !== message.groupId || store.getters.GET_IM_CHART_DIALOG_VISIBLE === false) {
message.isRead = false
}
store.dispatch('PUSH_IM_CHART_DIALOG_GROUP_MESSAGE', message)
} else {
message.groupId = message.from
store.dispatch('PUSH_IM_CHART_DIALOG_GROUP_MESSAGE', message)
}
},
// 穿透消息
onCmdMessage(message) {
message.sourceMsg = '发起对话'
message.data = '发起对话'
message = that.createClassNameAndTime(message)
let type = dotData(message, 'type')
if (type === 'groupchat') {
message.from = message.to
}
store.dispatch('PUSH_IM_CHART_DIALOG_GROUP_MESSAGE', message)
},
// 图片消息
onPictureMessage(message) {
message = that.createClassNameAndTime(message)
message.fileType = 'image'
let type = dotData(message, 'type')
if (type === 'groupchat') { // 群组消息
message.groupId = message.to
if (store.getters.GET_IM_CHART_DIALOG_GROUP_ID !== message.groupId || store.getters.GET_IM_CHART_DIALOG_VISIBLE === false) {
message.isRead = false
}
store.dispatch('PUSH_IM_CHART_DIALOG_GROUP_MESSAGE', message)
}
},
// 音频消息
onAudioMessage(message) {
let options = {url: message.url} options.onFileDownloadComplete = (response) => {
message.objectURL = that.WebIM.utils.parseDownloadResponse.call(that.getConnection(), response)
message = that.createClassNameAndTime(message)
message.fileType = 'audio'
let type = dotData(message, 'type')
if (type === 'groupchat') { // 群组消息
message.groupId = message.to
if (store.getters.GET_IM_CHART_DIALOG_GROUP_ID !== message.groupId || store.getters.GET_IM_CHART_DIALOG_VISIBLE === false) {
message.isRead = false
}
store.dispatch('PUSH_IM_CHART_DIALOG_GROUP_MESSAGE', message)
}
} options.onFileDownloadError = () => {
// 音频下载失败
} // 通知服务器将音频转为mp3
options.headers = {
'Accept': 'audio/mp3'
} that.WebIM.utils.download.call(that.getConnection(), options)
},
onEmojiMessage: (message) => {
let data = message.data
for (let i = 0, l = data.length; i < l; i++) {
}
},
onError(message) {
that.handError(message)
}
})
return this.connection
} // 处理报错
handError(error) {
let err = 'im报错:'
console.error(error)
switch (error.type) {
case 7 :
alert(err + '客户端网络中断')
break
case 8 :
alert(err + '多端登录,被踢下线')
break
case 16 :
alert(err + '服务端关闭了链接')
break
case 31 :
alert(err + '处理下行消息出错,try/catch抛出异常')
break
case 32 :
alert(err + '客户端断线')
break
case 33 :
alert(err + '客户端退出登录')
break
case 34 :
alert(err + '同一浏览器打开标签页超过上限')
break
case 402 :
alert(err + '取到token 但是没有连上xmpp server')
break
default:
alert(err + JSON.stringify(error))
}
} // 登出
logout() {
if (this.connection) {
this.connection.close()
}
} // 发送消息
sendMessage(message, to, config = {
type: 'txt',
success() {
},
fail() {
},
chatType: ''
}) {
const that = this
const id = this.getConnection().getUniqueId()
let ImMessage = this.WebIM.message
const type = config.type
const chatType = config.chatType
let msg = new ImMessage(type, id)
var option = {
msg: message,
to,
roomType: false,
success: (messageId, msgId) => {
let temp = {
id: messageId,
type,
from: that.username,
to,
data: message,
sourceMsg: message,
error: false,
msgId
}
if (config.isGroup) {
temp.groupId = to
} else {
temp.groupId = temp.to
}
temp = that.appendMessage(temp, 'out')
store.dispatch('PUSH_IM_CHART_DIALOG_GROUP_MESSAGE', temp)
config.success()
},
fail: () => {
config.fail()
}
}
if (chatType === 'chatRoom') {
option.chatType = 'chatRoom'
}
option = this._setExt(option)
msg.set(option)
if (chatType === 'chatRoom') {
msg.setGroup('groupchat')
}
this.getConnection().send(msg.body)
} // 添加消息
unShiftMessage(message) {
const className = message.from === this.username ? 'out' : 'in'
console.log(message.from, this.username, className)
const temp = this.appendMessage(message, className)
store.dispatch('UN_SHIFT_IM_CHART_DIALOG_GROUP_MESSAGE', temp)
} // 发送文本消息
sendTextMessage(message, to, success = () => {
}, fail = () => {
}) {
this.sendMessage(message, to, {type: 'txt', success, fail})
} // 发送组消息
sendTextMessageGroup(message, groupId, success) {
this.sendMessage(message, groupId, {type: 'txt', success, chatType: 'chatRoom', isGroup: true})
} // 发送cmd 消息
sendCmdMessage(message, to, success = () => {
}, fail = () => {
}, action = '[CMDMessage]') {
// todo
} /**
* 发送图片
* @param input
* @param to
*/
sendGroupImageMessage(input, to, success = () => {
}, fail = () => {
}, chatType = 'chatRoom') {
const conn = this.getConnection()
const id = conn.getUniqueId() // 生成本地消息id
let ImMessage = this.WebIM.message
const msg = new ImMessage('img', id) // 创建图片消息
// var input = document.getElementById('image') // 选择图片的input
const file = this.WebIM.utils.getFileUrl(input) // 将图片转化为二进制文件
const allowType = {
'jpg': true,
'gif': true,
'png': true,
'bmp': true,
'jpeg': true
}
const that = this
if (file.filetype.toLowerCase() in allowType) {
let option = {
apiUrl: this.config.apiURL,
file: file,
to, // 接收消息对象
roomType: false,
chatType,
onFileUploadError: () => { // 消息上传失败
alert('图片发送失败')
fail()
},
onFileUploadComplete: (file) => { // 消息上传成功
if (Array.isArray(file.entities)) {
for (let v of file.entities) {
let url = file.uri + '/' + v.uuid
let temp = {
id,
type: 'chat',
from: that.username,
to,
url,
error: false,
groupId: to
}
temp = that.appendMessage(temp, 'out')
temp.fileType = 'image'
store.dispatch('PUSH_IM_CHART_DIALOG_GROUP_MESSAGE', temp)
success()
}
}
},
flashUpload: this.WebIM.flashUpload
}
option = this._setExt(option)
msg.set(option)
if (chatType === 'chatRoom') {
msg.setGroup('groupchat')
} else {
msg.body.chatType = chatType
}
conn.send(msg.body)
} else {
alert('只支持:' + allowType.join(',') + '的图片格式')
}
}
} export default Easemob
4. 初始化 登录,获取好友分组以及聊天信息,使用场景系统用户自接登录,所以这里没有单独做注册登录
computed: {// 用户
rosterList () {
return this.GET_IM_CHART_ROSTERLIST || []
},
groupList () {
return this.GET_IM_CHART_GROUPLIST || []
},
// 聊天数据
charts () {
return this.GET_IM_CHART_DIALOG_GROUP_MESSAGE(this.imTo.toId) || []
},
...mapGetters({
GET_IM_CHART_ROSTERLIST: 'GET_IM_CHART_ROSTERLIST',
GET_IM_CHART_GROUPLIST: 'GET_IM_CHART_GROUPLIST',
GET_IM_CHART_DIALOG_GROUP_MESSAGE: 'GET_IM_CHART_DIALOG_GROUP_MESSAGE',
GET_IM_CHART_DIALOG_GROUP_ID: 'GET_IM_CHART_DIALOG_GROUP_ID'
})
},methods: {
initIm () {
// im 登陆
this.im.login('test1', '123456')
},
// 发送消息
sendMessage () {
if (this.imTo.chatType === 'single') {
// 会话
this.im.sendTextMessage(this.txt, this.imTo.toId, () => {
this.txt = ''
})
} else {
// 组
this.im.sendTextMessageGroup(this.txt, this.imTo.toId, () => {
this.txt = ''
})
}
},
// 发送图片消息
sendImageMessage () {
if (!this.imTo.toId) {
alert('发送对象必须')
return
}
this.im.sendGroupImageMessage(this.$refs.imageInput, this.imTo.toId, () => {
this.fileTrigger = !this.fileTrigger
this.$nextTick(_ => {
this.fileTrigger = !this.fileTrigger
})
})
},
toChats (opt) {
if (opt.groupid) {
this.imTo = {
chatType: 'group',
toId: opt.groupid,
toName: opt.groupname
}
} else {
this.imTo = {
chatType: 'single',
toId: opt.name,
toName: opt.name
}
}
},
scrollHandler () {},
// 滚动到底
scrollToBottom () {
this.$nextTick(_ => {
this.$refs['chattingContent'].wrap.scrollTop = this.$refs['chattingContent'].wrap.scrollHeight
})
}
}
git地址:
https://github.com/DarkSide7H/vue-chat
示例截图