import utils from '@/uni_modules/uni-im/common/utils.js'; import MsgManager from './MsgManager.js'; import createObservable from './createObservable'; const db = uniCloud.database(); const uniImCo = uniCloud.importObject("uni-im-co", { customUI: true }) function current_uid() { return uniCloud.getCurrentUserInfo().uid } const state = createObservable({ // 会话数据 conversation: { dataList: [], hasMore: true, loading: false // 加锁防止意外重复请求时出错 }, // 正在对话的会话id currentConversationId: false, // 全局响应式心跳,用于更新消息距离当前时长 等 heartbeat: '', // 好友列表 friend: { dataList: [], hasMore: true }, // 群列表 group: { dataList: [], hasMore: true }, // 系统通知消息 notification: { dataList: [], hasMore: true }, //存储所有出现过的用户信息,包括群好友信息 usersInfo: {}, //是否为pc宽屏 isWidescreen: false, //系统信息 systemInfo: {}, // #ifndef H5 indexDB: false, // #endif audioContext: false, // sqlite数据库是否已经打开 dataBaseIsOpen: false, // 记录socket连接次数(用于处理,断开重连) socketOpenIndex: 0 }) const methods = { /** * 会话对象 * data:会话对象数据模型(conversationDatas是原始数据,data为经过转化的数据) * loadMore:加载更多数据方法 */ conversation: { // 撤回消息,参数: 消息id 会话id ,操作者id async revokeMsg({ msg_id, _id, conversation_id, user_id = false, create_time }) { if (_id) { msg_id = _id } // console.log({ // msg_id, // conversation_id, // user_id // }); // console.log('msg_id---',msg_id); // userId为操作者id,false表示当前用户操作的。需要广播同步给其他人。收到其他人的撤回指令,会带上触发者的id if (!user_id) { // 为空表示当前用户就是操作者 try { let res = await uniImCo.revokeMsg(msg_id) // console.log('res', res); } catch (err) { console.log('err', err); return uni.showToast({ title: err.message, icon: 'none' }); } } let conversation = await this.get(conversation_id) // 处理列表显示 let msgList = conversation.msgList let index = msgList.findIndex(item => item._id == msg_id) // console.log('index',index); if (index != -1) { let msg = msgList[index] // console.log(111111112222,JSON.stringify(msg)); msg.is_revoke = true msg.body = '[此消息已被撤回]' conversation.msgList.splice(index, 1, Object.assign({}, msg)) // console.log('conversation.msgList', conversation.msgList); } // 删除本地存储中的数据 let localMsgs = await conversation.msgManager.localMsg.get({ "minTime": create_time - 1, "maxTime": create_time + 1 }) let localMsg = localMsgs.find(item => item._id == msg_id) // console.log('res查本地存储中是否有这条消息',localMsgs,localMsg); if (localMsg) { localMsg.is_revoke = true localMsg.body = '[此消息已被撤回]' conversation.msgManager.localMsg.update(localMsg.unique_id, localMsg) } }, async get(param) { /** * 字符串 会话id * 数组 一组会话id(暂不支持) * 对象类型 会话对方信息(用于如果本地不存在则需要自动创建的场景),包括:{friend_uid,to_uid,from_uid,group_id,user_info} */ let conversationId = false if (param) { if (typeof param == 'object') { let { friend_uid, user_id, group_id, conversation_id } = param conversationId = conversation_id if (user_id) { friend_uid = user_id param.friend_uid = user_id } if (!conversationId) { if (!group_id && !friend_uid) { console.log('param---------', param); throw new Error("会话对象不详,请检查参数", param) } conversationId = utils.getConversationId(friend_uid || group_id, friend_uid ? 'single' : 'group') } } else if (typeof param == 'string') { conversationId = param } else { throw new Error("会话对象不详,请检查参数", param) } } let conversationDatas = state.conversation.dataList if (conversationId) { conversationDatas = conversationDatas.filter(i => i.id == conversationId) if (conversationDatas.length == 0) { // 本地没有没有就联网查找 let conversationData = await this.loadMore(conversationId) if (conversationData) { conversationDatas = [conversationData] } else { if (param.group_id) { throw new Error("未找到此群会话") } if (typeof param != 'object') { console.log('param', param); throw new Error("参数错误") } // 非群会话,网络也没有就本地创建一个 if (!param.user_info) { let res = await uniCloud.database() .collection('uni-id-users') .doc(param.friend_uid) .field('_id,nickname,avatar_file') .get() console.log('user_info', res) param.user_info = res.result.data[0] // console.log('param.user_info', param.user_info); if (!param.user_info) { throw new Error("用户查找失败") } } let conversationData = { group_id: param.group_id, friend_uid: param.friend_uid, unread_count: 0 } try { const db = uniCloud.database(); let res = await db.collection('uni-im-conversation').add(conversationData) // console.log('res',res) } catch (e) { throw new Error(e) } conversationData = Object.assign(conversationData, { user_id: current_uid(), id: conversationId, user_info: param.user_info, type: param.friend_uid ? 1 : 2, msgList: [], update_time: Date.now() }) this.add(conversationData) conversationDatas.push(conversationData) } } } // console.log('conversationDatas---',conversationDatas,param); // console.log('conversationDatas---',conversationDatas.length,param); // console.log('999conversationDatas',conversationDatas,conversationId); if (conversationId) { let conversationData = conversationDatas[0] // 指定获取某个id的群会话时,判断如果群会话的 群成员为空就从云端拉取 if (conversationData.group_id && Object.keys(conversationData.group_member).length == 0) { let res = await db.collection( db.collection('uni-im-group-member').where({ group_id: conversationData.group_id }).getTemp(), db.collection('uni-id-users').field('_id,nickname,avatar_file').getTemp() ) .limit(1000) .get() let group_member = {} res.result.data.forEach(item => { let usersInfo = item.user_id[0] group_member[usersInfo._id] = usersInfo }) methods.mergeUsersInfo(group_member) conversationData.group_member = group_member // console.log('conversationData.group_member', conversationData.group_member) } // console.log('conversationData*-*--*-**-',conversationData) // #ifdef APP if (!conversationData.isInit) { conversationData.msgManager = new MsgManager(conversationData) } // #endif return conversationData } else { return conversationDatas } }, async loadMore(conversation_id) { // console.log('loadMore-----','loadMore') if (!conversation_id) { // console.log('state.conversation.loading',state.conversation.loading) //上一次正在调用,下一次不能马上开始 if (state.conversation.loading) { // console.log('加载中') return [] } else { state.conversation.loading = true } } let conversationDatas = await this.get() let lastConversation = conversationDatas[conversationDatas.length - 1] // console.log('conversationDatas.length',conversationDatas.length,lastConversation) let maxUpdateTime = lastConversation ? lastConversation.update_time : '' if (conversation_id) { // 已有会话id的情况下,不设置更新时间条件 maxUpdateTime = '' } let res = { data: [] } try { res = await uniImCo.getConversationList({ maxUpdateTime, limit: 30, conversation_id }) } catch (e) { console.log(e) if (!conversation_id) { state.conversation.loading = false } } if (res.data.length) { // console.log('getConversationList res', res, { // maxUpdateTime, // limit: 30, // conversation_id // }); this.add(res.data) } if (!conversation_id) { state.conversation.loading = false state.conversation.hasMore = (res.data.length == 30) /** * 处理云端任务,比如撤回消息等 */ let whereString = "user_id == $cloudEnv_uid" let group_ids = res.data.filter(item => item.group_id).map(i => i.group_id) || [] if (group_ids.length) { whereString = `(user_id == $cloudEnv_uid || "group_id" in ${JSON.stringify(group_ids)})` } let lastTaskTime = uni.getStorageSync('uni-im-lastTaskTime') // console.log('lastTaskTime',lastTaskTime); if (lastTaskTime) { whereString += `&& "create_time" > ${lastTaskTime}` } db.collection('uni-im-task') .where(whereString) .orderBy('create_time desc') .get() .then((e) => { // console.log('uni-im-task', e.result.data); if (e.result.data.length) { e.result.data.forEach(item => { if (item.type == "revoke_msg") { this.revokeMsg(item.payload) } }) uni.setStorageSync('uni-im-lastTaskTime', e.result.data[0].create_time) } }) .catch(e => { console.error(e); }) return res.data } else { return res.data[0] } }, add(data) { if (!Array.isArray(data)) { data = [data] } data.forEach(item => { // 服务端联查到的数据,群和用户信息是数组。这里兼容 客户端add时可直接传object if (Array.isArray(item.user_info)) { item.user_info = item.user_info[0] } if (Array.isArray(item.group_info)) { item.group_info = item.group_info[0] if (item.group_id) { if (!item.group_member) { item.group_member = {} } if (item.group_info.introduction === undefined) { item.group_info.introduction = '' } if (item.group_info.avatar_file === undefined) { item.group_info.avatar_file = { url: "" } } } } // item.chatText = "" // item.isInit = false // item.title = "" // item.avatar_file = {} item = Object.assign(item, { isInit: false, title: "", chatText: "", avatar_file: {}, call_list: [] }) if (item.user_info) { Object.defineProperties(item, { title: { get() { return item.user_info.nickname } }, avatar_file: { get() { return item.user_info.avatar_file } }, group_info: { value: false } }) } else if (item.group_info) { Object.defineProperties(item, { title: { get() { return item.group_info.name } }, avatar_file: { get() { return item.group_info.avatar_file } }, user_info: { value: false } }) } else { console.error('会话列表失效,疑似关联用户/群被删除(请改为软删除避免系统异常):', JSON.stringify(item)); } let update_time = item.update_time Object.defineProperties(item, { last_msg_note: { get() { let last_msg_note = "暂无记录" let last_msg = item.msgList[item.msgList.length - 1] // console.log('---last_msg',last_msg) if (item.chatText && state.currentConversationId != item.id) { last_msg = { body: "[uni-im-draft]" + item.chatText, type: 'text', create_time: Date.now() } } if (last_msg) { last_msg_note = '[多媒体]' if (last_msg.type == 'text') { last_msg_note = last_msg.body.toString() last_msg_note = last_msg_note.replace(/[\r\n]/g, ""); last_msg_note = last_msg_note.slice(0, 30) } if (last_msg.is_revoke) { last_msg_note = "消息已被撤回" } if (last_msg.is_delete) { last_msg_note = "消息已被删除" } } return last_msg_note } }, update_time: { get() { let last_msg = item.msgList[item.msgList.length - 1] if (last_msg) { return last_msg.create_time } else { return update_time } } } }) // 把user_info统一存到一个对象 let { user_info, group_member } = item let usersInfo = {} if (user_info) { usersInfo[user_info._id] = user_info } methods.mergeUsersInfo(usersInfo) item.msgManager = new MsgManager(item) // console.log('state.conversation.dataList',state.conversation.dataList); let initMsg = (msgList) => { for (let i = 0; i < msgList.length; i++) { let msg = msgList[i] if (msg && typeof msg == 'object') { if (!('is_delete' in msg)) { msg.is_delete = false } } } let methodsList = ['push', 'unshift'] // #ifdef VUE2 let arr_methods = Object.create(Array.prototype); methodsList.forEach(methods => { arr_methods[methods] = function() { // console.log(`使用的是${methods}方法`) initMsg(arguments) Array.prototype[methods].apply(this, arguments) } }) // #endif // #ifdef VUE3 methodsList.forEach(methods => { msgList[methods] = function() { // console.log(`使用的是${methods}方法`) initMsg(arguments) Array.prototype[methods].apply(this, arguments) } }); // #endif return msgList } initMsg(item.msgList); item.msgList.clear = function() { // console.log('clear'); // #ifdef VUE3 this.length = 0 // #endif // #ifdef VUE2 item.msgList = [] initMsg(item.msgList) // #endif } // #ifdef VUE3 Object.defineProperty(item, 'msgList', { writable: false }) // #endif // 判断是否存在,再新增。 if (!state.conversation.dataList.find(conversation => conversation.id == item.id)) { state.conversation.dataList.push(item) } else { // console.log('重复新增已存在的会话', item) } }) // console.log('存到storage',state.conversation) // 存到storage uni.setStorage({ key: 'uni-im-conversation' + '_uid:' + current_uid(), data: state.conversation }) return data }, // 统计所有消息的未读数 unreadCount() { let conversationDatas = state.conversation.dataList return conversationDatas.reduce((sum, item, index, array) => sum + item.unread_count, 0) }, remove(id) { let index = state.conversation.dataList.findIndex(i => i.id == id) state.conversation.dataList.splice(index, 1) } }, /** * 系统消息 */ notification: { get: ({ type, excludeType } = {}) => { const notificationDatas = state.notification.dataList if (notificationDatas) { return notificationDatas.reduce((sum, item) => { // 指定需要的类型 if (type) { //兼容字符串和数组 typeof type == 'string' ? type = [type] : '' if (type.includes(item.payload.subType)) { sum.push(item) } // 排查指定的类型 } else if (excludeType) { //兼容字符串和数组 typeof excludeType == 'string' ? excludeType = [excludeType] : '' if (!excludeType.includes(item.payload.subType)) { sum.push(item) } } else { sum.push(item) } return sum }, []) } else { return false } }, async loadMore() { let res = await db.collection('uni-im-notification') .aggregate() .match('"payload.type" == "uni-im-notification" && "user_id" == $cloudEnv_uid') .sort({ create_time: -1 }) .limit(1000) .end() this.add(res.result.data) this.hasMore == (res.result.data.length != 0) }, add(datas) { if (!Array.isArray(datas)) { datas = [datas] } let notificationDatas = datas.concat(state.notification.dataList) // 正序,实现时间大的覆盖时间小的 notificationDatas.sort((a, b) => a.create_time - b.create_time) // console.log('notificationDatas',notificationDatas); let obj = {} for (var i = 0; i < notificationDatas.length; i++) { let item = notificationDatas[i] // 去重操作 let { subType, unique } = item.payload obj[unique ? (subType + "_" + unique) : (Date.now() + "_" + i)] = item } let dataList = [] for (let key in obj) { let item = obj[key] dataList.push(item) } // 倒序 实现,最新的消息在最上面 dataList.sort((a, b) => b.create_time - a.create_time) // console.log('dataList',dataList) state.notification.dataList = dataList }, unreadCount(param = {}) { let notificationDatas = this.get(param) let unreadCount = notificationDatas.reduce((sum, item, index, array) => { if (!item.is_read) { sum++ } return sum }, 0) // console.log('最新的未读数:', unreadCount, data); // 注意:在非tabbar页面无法设置 badge if (unreadCount === 0) { uni.removeTabBarBadge({ index: 2, complete: (e) => { // console.log(e) } }) } else { uni.setTabBarBadge({ index: 2, text: unreadCount + '', complete: (e) => { // console.log(e) } }) } if (unreadCount) { return unreadCount + '' } else { return '' } } }, friend: { get() { return state.friend.dataList }, async loadMore({ friend_uid } = {}) { let whereString = '"user_id" == $cloudEnv_uid' if (friend_uid) { whereString += `&& "friend_uid" == "${friend_uid}"` // console.log('whereString',whereString); } let res = await db.collection( db.collection('uni-im-friend').where(whereString).field('friend_uid,mark,class_name') .getTemp(), db.collection('uni-id-users').field('_id,nickname,avatar_file').getTemp() ) .limit(500) .get() let data = res.result.data // console.log('data',data); data.forEach((item, index) => { data[index] = item.friend_uid[0] let uid = data[index]._id if (!state.usersInfo[uid]) { state.usersInfo[uid] = item.friend_uid[0] } }) state.friend.hasMore = data.length == 500 state.friend.dataList.push(...data) }, remove(friend_uid) { let friendList = state.friend.dataList let index = friendList.findIndex(i => i._id == friend_uid) friendList.splice(index, 1) } }, group: { get() { return state.group.dataList }, async loadMore({ group_id } = {}) { let whereString = '"user_id" == $cloudEnv_uid ' if (group_id) { whereString += `&& "group_id" == "${group_id}"` } // console.log('whereString',whereString) let res = await db.collection( db.collection('uni-im-group-member').where(whereString).getTemp(), db.collection('uni-im-group').getTemp() ) .limit(500) .get() res.result.data.map(item => { item.group_info = item.group_id[0] delete item.group_id return item }) // 过滤掉已经被删除的群 res.result.data = res.result.data.filter(item=>item.group_info) state.group.hasMore = res.result.data.length == 500 if (group_id) { state.group.dataList.push(...res.result.data) } else { state.group.dataList = res.result.data } }, remove({ group_id }) { let groupList = state.group.dataList let index = groupList.findIndex(i => i.group_info._id == group_id) // console.log('index',group_id,index,groupList) if (index != -1) { groupList.splice(index, 1) // console.log('state.group.dataList',state.group.dataList) } }, }, mergeUsersInfo(usersInfo) { state.usersInfo = Object.assign({}, state.usersInfo, usersInfo) }, async clearUnreadCount(conversation_id) { let conversation = await this.conversation.get(conversation_id) setTimeout(function() { conversation.unread_count = 0 }, 10); // 触发器会触发消息表的 is_read = true uniCloud.database() .collection('uni-im-conversation') .where({ user_id: current_uid(), id: conversation_id }) .update({ "unread_count": 0 }).then(e => { console.log('设置为已读', e.result.updated); }) } } const mapState = function(keys = []) { let obj = {} keys.forEach((key) => { let keyName = key, keyCName = false if (key.includes(' as ')) { let _key = key.trim().split(' as ') keyName = _key[0] keyCName = _key[1] } obj[keyCName || keyName] = function() { return state[keyName] } }) return obj } export default deepAssign(state, methods, { mapState }) /** *深度合并多个对象的方法 */ function deepAssign() { let len = arguments.length, target = arguments[0] if (!isPlainObject(target)) { target = {} } for (let i = 1; i < len; i++) { let source = arguments[i] if (isPlainObject(source)) { for (let s in source) { if (s === '__proto__' || target === source[s]) { continue } if (isPlainObject(source[s])) { target[s] = deepAssign(target[s], source[s]) } else { target[s] = source[s] } } } } return target } /** *判断对象是否是一个纯粹的对象 */ function isPlainObject(obj) { return typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object Object]' }