import TIM from 'tim-wx-sdk'; class TIMManager { constructor() { this.tim = null; this.isLogin = false; this.userId = null; this.messageCallbacks = []; this.messageReadCallbacks = []; // 新增:缓存 store 实例,避免重复获取 this.store = null; } /** * 初始化 TIM SDK * @param {Number} sdkAppID - 腾讯云 IM 的 SDKAppID */ init(sdkAppID) { // 创建 SDK 实例 this.tim = TIM.create({ SDKAppID: sdkAppID }); // 设置日志级别(开发时设为 0,生产设为 1) this.tim.setLogLevel(0); // 注册监听事件 this.registerEvents(); // 初始化时提前尝试获取 store(提升后续获取成功率) this.initStore(); } /** * 初始化 store 实例(核心修复:提前获取+缓存) */ initStore() { try { // 方式1:从全局 App 实例获取 const app = getApp(); if (app && app.$store) { this.store = app.$store; console.log('✅ 从全局 App 获取 store 成功'); return; } // 方式2:直接导入 store(备用方案,注意路径正确性) const storeModule = require('../store/index.js'); if (storeModule && storeModule.default) { this.store = storeModule.default; console.log('✅ 直接导入 store 成功'); return; } console.warn('⚠️ 初始化时未获取到 store,将在需要时重试'); } catch (error) { console.warn('⚠️ 初始化 store 失败:', error.message); } } /** * 安全获取 store 实例(带重试逻辑) * @returns {Promise} store 实例 */ async getStoreSafely() { // 缓存中有则直接返回 if (this.store) { return this.store; } // 无缓存时,重试获取(最多等 3 秒,每 100ms 检查一次) return new Promise((resolve, reject) => { let retryCount = 0; const maxRetry = 30; // 30 * 100ms = 3 秒 const timer = setInterval(() => { retryCount++; // 尝试从全局 App 获取 const app = getApp(); if (app && app.$store) { clearInterval(timer); this.store = app.$store; resolve(this.store); return; } // 尝试直接导入 try { const storeModule = require('../store/index.js'); if (storeModule && storeModule.default) { clearInterval(timer); this.store = storeModule.default; resolve(this.store); return; } } catch (e) {} // 超过重试次数 if (retryCount >= maxRetry) { clearInterval(timer); reject(new Error('获取 store 超时(3秒)')); } }, 100); }); } /** * 注册事件监听 */ registerEvents() { // SDK 进入 ready 状态 this.tim.on(TIM.EVENT.SDK_READY, this.onSdkReady.bind(this)); // SDK 未 ready this.tim.on(TIM.EVENT.SDK_NOT_READY, this.onSdkNotReady.bind(this)); // 收到新消息 this.tim.on(TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived.bind(this)); // 会话列表更新(核心修复) this.tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated.bind(this)); // 消息已读回执(对方已读我发送的消息) this.tim.on(TIM.EVENT.MESSAGE_READ_BY_PEER, this.onMessageReadByPeer.bind(this)); // 被踢下线 this.tim.on(TIM.EVENT.KICKED_OUT, this.onKickedOut.bind(this)); // 网络状态变化 this.tim.on(TIM.EVENT.NET_STATE_CHANGE, this.onNetStateChange.bind(this)); } /** * SDK Ready */ onSdkReady() { this.isLogin = true; } /** * SDK Not Ready */ onSdkNotReady() { this.isLogin = false; } /** * 收到新消息 */ onMessageReceived(event) { const messageList = event.data; // 同步接收到的消息到MySQL(双重保障) messageList.forEach(message => { this.syncReceivedMessageToMySQL(message); }); // 触发回调 this.messageCallbacks.forEach(callback => { callback(messageList); }); } /** * 同步接收到的消息到MySQL */ async syncReceivedMessageToMySQL(timMessage) { try { // 获取消息类型 const getMessageType = (msg) => { const typeMap = { 'TIMTextElem': 1, 'TIMImageElem': 2, 'TIMSoundElem': 3, 'TIMVideoFileElem': 4, 'TIMFileElem': 5 }; // 处理自定义消息(我们的语音消息) if (msg.type === 'TIMCustomElem' && msg.payload) { try { const customData = JSON.parse(msg.payload.data); if (customData.type === 'voice') { return 3; // 语音消息 } } catch (e) { console.error('解析自定义消息类型失败:', e); } } return typeMap[msg.type] || 1; }; // 获取消息内容 const getMessageContent = (msg) => { switch (msg.type) { case 'TIMTextElem': return msg.payload.text || ''; case 'TIMImageElem': return '[图片]'; case 'TIMSoundElem': return '[语音]'; case 'TIMVideoFileElem': return '[视频]'; case 'TIMFileElem': return '[文件]'; case 'TIMCustomElem': // 处理自定义消息 try { const customData = JSON.parse(msg.payload.data); if (customData.type === 'voice') { return '[语音]'; } } catch (e) { console.error('解析自定义消息内容失败:', e); } return '[自定义消息]'; default: return '[未知消息]'; } }; // 使用V2接口,支持红娘消息(TIM用户ID可能是 m_xxx 格式) const syncData = { messageId: timMessage.ID, fromTimUserId: String(timMessage.from), // 保持字符串格式,支持 m_xxx toTimUserId: String(timMessage.to), // 保持字符串格式,支持 m_xxx messageType: getMessageType(timMessage), content: getMessageContent(timMessage), sendTime: timMessage.time }; // 如果是语音消息,添加语音信息 if (timMessage.type === 'TIMSoundElem' && timMessage.payload) { syncData.mediaUrl = timMessage.payload.url || timMessage.payload.remoteAudioUrl; syncData.duration = timMessage.payload.second || 0; syncData.mediaSize = timMessage.payload.size || 0; } // 如果是自定义消息(我们的语音消息) if (timMessage.type === 'TIMCustomElem' && timMessage.payload) { try { const customData = JSON.parse(timMessage.payload.data); if (customData.type === 'voice') { syncData.mediaUrl = customData.url; syncData.duration = customData.duration; syncData.mediaSize = customData.size || 0; } } catch (e) { console.error('解析自定义消息失败:', e); } } // 如果是图片消息,添加图片信息 if (timMessage.type === 'TIMImageElem' && timMessage.payload.imageInfoArray) { const imageInfo = timMessage.payload.imageInfoArray[0]; syncData.mediaUrl = imageInfo.imageUrl; syncData.thumbnailUrl = imageInfo.imageUrl; } // 调用后端同步接口(V2版本,支持红娘消息) const res = await uni.request({ url: 'https://api.zhongruanke.cn/api/chat/syncTIMMessageV2', method: 'POST', data: syncData, header: { 'Content-Type': 'application/json' } }); if (res[1].data.code === 200) { const msgType = res[1].data.type === 'matchmaker' ? '红娘消息' : '用户消息'; } } catch (error) { console.error('❌ 同步接收消息失败:', error); // 同步失败不影响主流程 } } /** * 消息已读回执(对方已读我发送的消息) */ onMessageReadByPeer(event) { // 触发所有已读回执回调 this.messageReadCallbacks.forEach(callback => { try { callback(event); } catch (error) { console.error('❌ 已读回执回调执行失败:', error); } }); } /** * 会话列表更新(核心修复:异步安全获取 store) */ async onConversationListUpdated(event) { // 计算总未读数 const conversationList = event.data; let totalUnread = 0; conversationList.forEach(conversation => { totalUnread += conversation.unreadCount || 0; }); try { // 核心修复:异步安全获取 store const store = await this.getStoreSafely(); if (store) { await store.dispatch('updateTotalUnread', totalUnread); console.log('✅ 全局未读数更新成功:', totalUnread); } // 兜底方案:触发自定义事件,确保页面能收到更新 uni.$emit('conversationUnreadUpdate', totalUnread); } catch (error) { console.error('❌ 更新全局未读数失败:', error.message); // 即使失败,仍触发自定义事件 uni.$emit('conversationUnreadUpdate', totalUnread); } } /** * 被踢下线 */ onKickedOut(event) { uni.showModal({ title: '下线通知', content: '您的账号在其他设备登录', showCancel: false }); } /** * 网络状态变化 */ onNetStateChange(event) {} /** * 登录 */ async login(userID, userSig) { try { const res = await this.tim.login({ userID: String(userID), userSig: userSig }); this.userId = userID; return res; } catch (error) { console.error('❌ TIM 登录失败:', error); throw error; } } /** * 登出 */ async logout() { try { await this.tim.logout(); this.isLogin = false; this.userId = null; } catch (error) { console.error('❌ TIM 登出失败:', error); } } /** * 发送文本消息 */ async sendTextMessage(toUserId, text) { try { // 验证参数 if (!toUserId || toUserId === 'undefined' || toUserId === 'null') { console.error('❌ 接收者ID无效:', toUserId); throw new Error('接收者ID无效: ' + toUserId); } if (!this.userId || this.userId === 'undefined' || this.userId === 'null') { console.error('❌ 发送者ID无效:', this.userId); throw new Error('发送者ID未登录或无效'); } const toUserIdStr = String(toUserId); // 创建文本消息 const message = this.tim.createTextMessage({ to: toUserIdStr, conversationType: TIM.TYPES.CONV_C2C, // 单聊 payload: { text: text } }); // 发送消息 const res = await this.tim.sendMessage(message); return res.data.message; } catch (error) { console.error(' - 错误代码:', error.code || 'N/A'); throw error; } } /** * 发送图片消息 */ async sendImageMessage(toUserId, filePath) { try { const message = this.tim.createImageMessage({ to: String(toUserId), conversationType: TIM.TYPES.CONV_C2C, payload: { file: filePath }, onProgress: (percent) => {} }); const res = await this.tim.sendMessage(message); return res.data.message; } catch (error) { console.error('❌ 图片发送失败:', error); throw error; } } /** * 发送语音消息(通过自定义消息携带MinIO URL) * @param {String} toUserId 接收者ID * @param {String} voiceUrl MinIO语音文件URL * @param {Number} duration 语音时长(秒) * @param {Number} fileSize 文件大小(字节) */ async sendVoiceMessage(toUserId, voiceUrl, duration, fileSize) { try { // 使用自定义消息发送语音信息 const message = this.tim.createCustomMessage({ to: String(toUserId), conversationType: TIM.TYPES.CONV_C2C, payload: { data: JSON.stringify({ type: 'voice', url: voiceUrl, duration: duration, size: fileSize }), description: '[语音]', extension: voiceUrl } }); const res = await this.tim.sendMessage(message); return res.data.message; } catch (error) { console.error('❌ 语音发送失败:', error); throw error; } } /** * 获取会话列表 */ async getConversationList() { try { const res = await this.tim.getConversationList(); return res.data.conversationList; } catch (error) { console.error('❌ 获取会话列表失败:', error); throw error; } } /** * 获取聊天记录 */ async getMessageList(conversationID, count = 15) { try { const res = await this.tim.getMessageList({ conversationID: conversationID, count: count }); return res.data.messageList; } catch (error) { console.error('❌ 获取聊天记录失败:', error); throw error; } } /** * 将消息设为已读 */ async setMessageRead(conversationID) { try { await this.tim.setMessageRead({ conversationID }); } catch (error) { console.error('❌ 设置已读失败:', error); } } /** * 撤回消息 */ async revokeMessage(message) { try { await this.tim.revokeMessage(message); } catch (error) { console.error('❌ 撤回失败:', error); throw error; } } /** * 监听消息 */ onMessage(callback) { this.messageCallbacks.push(callback); } /** * 移除监听 */ offMessage(callback) { const index = this.messageCallbacks.indexOf(callback); if (index > -1) { this.messageCallbacks.splice(index, 1); } } /** * 监听已读回执 */ onMessageRead(callback) { this.messageReadCallbacks.push(callback); } /** * 移除已读回执监听 */ offMessageRead(callback) { const index = this.messageReadCallbacks.indexOf(callback); if (index > -1) { this.messageReadCallbacks.splice(index, 1); } } /** * 获取 TIM 实例 */ getTim() { return this.tim; } /** * 获取当前登录的 IM 用户ID */ getCurrentUserId() { return this.userId ? String(this.userId) : null; } } // 导出单例 const timManager = new TIMManager(); export default timManager;