// 引入腾讯IM SDK import TIM from 'tim-wx-sdk' import TIMUploadPlugin from 'tim-upload-plugin' import api from './api.js' const env = __VITE_ENV__; // 腾讯IM配置 - SDKAppID 必须是 number 类型,使用 Number() 转换 const TIM_CONFIG = { SDKAppID: Number(env.MP_TIM_SDK_APP_ID), // 患者端 IM SDKAppID } // 验证配置 if (!TIM_CONFIG.SDKAppID || isNaN(TIM_CONFIG.SDKAppID)) { console.error('❌ TIM SDK配置错误: MP_TIM_SDK_APP_ID未定义或无效', { envValue: env.MP_TIM_SDK_APP_ID, parsedValue: TIM_CONFIG.SDKAppID }) } // IM连接配置常量 const IM_CONNECTION_CONFIG = { MAX_RECONNECT_ATTEMPTS: 10, // 最大重连次数 RECONNECT_DELAYS: [2000, 4000, 8000, 16000, 30000], // 重连延迟(指数退避) LOGIN_COOLDOWN: 5000, // 登录冷却时间 SDK_READY_TIMEOUT: 15000, // SDK就绪超时时间 LOGIN_CHECK_INTERVAL_STABLE: 60000, // 稳定状态检查间隔 LOGIN_CHECK_INTERVAL_UNSTABLE: 15000, // 不稳定状态检查间隔 LOGIN_CHECK_FIRST_DELAY: 30000, // 首次检查延迟 HEARTBEAT_INTERVAL: 60000, // 心跳间隔(毫秒)60秒 HEARTBEAT_MAX_FAIL: 3, // 心跳最大失败次数 NETWORK_RECONNECT_DELAY: 2000, // 网络恢复后延迟重连 MESSAGE_BATCH_COUNT: 20, // 每批消息数量 MAX_MESSAGE_REQUESTS: 50, // 最大消息请求次数 MAX_CACHE_SIZE: 1000, // 最大缓存消息数 TIM_INSTANCE_READY_CHECK_INTERVAL: 100 // TIM实例就绪检查间隔 } class TimChatManager { constructor() { // TIM实例和会话 this.tim = null this.conversation = null // 用户信息 this.currentUserID = '' this.currentUserSig = '' // 消息管理 this.messageList = [] this.currentConversationID = null // 分页加载相关状态 this.nextReqMessageID = "" this.isCompleted = false this.isLoadingMore = false // 状态标志 this.isLoading = false this.isLoggedIn = false this.isInitializing = false this.isLoggingIn = false // 定时器 this.loginCheckInterval = null this.heartbeatInterval = null this.networkReconnectTimer = null // 重连管理 this.reconnectAttempts = 0 this.maxReconnectAttempts = IM_CONNECTION_CONFIG.MAX_RECONNECT_ATTEMPTS this.reconnectDelays = IM_CONNECTION_CONFIG.RECONNECT_DELAYS this.lastLoginTime = 0 this.loginCooldown = IM_CONNECTION_CONFIG.LOGIN_COOLDOWN // 心跳管理 this.heartbeatFailCount = 0 // 回调函数 this.callbacks = { onMessageReceived: null, onMessageSent: null, onSDKReady: null, onSDKNotReady: null, onError: null, onLoginStatusChanged: null, onConversationListUpdated: null } // 绑定事件处理函数 this.boundEventHandlers = { onSDKReady: this.onSDKReady.bind(this), onSDKNotReady: this.onSDKNotReady.bind(this), onMessageReceived: this.onMessageReceived.bind(this), onMessageSentSucceeded: this.onMessageSentSucceeded.bind(this), onMessageSentFailed: this.onMessageSentFailed.bind(this), onConversationListUpdated: this.onConversationListUpdated.bind(this), onNetStateChange: this.onNetStateChange.bind(this), onKickedOut: this.onKickedOut.bind(this) } } // ============== 资源管理方法 ============== // 清理所有定时器 clearAllTimers() { if (this.loginCheckInterval) { clearTimeout(this.loginCheckInterval) clearInterval(this.loginCheckInterval) this.loginCheckInterval = null } if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval) this.heartbeatInterval = null } if (this.networkReconnectTimer) { clearTimeout(this.networkReconnectTimer) this.networkReconnectTimer = null } console.log('所有定时器已清理') } // 清理缓存(已弃用缓存功能) cleanupCache() { // 缓存功能已移除 } // 重置所有状态 resetAllStates() { this.isLoggedIn = false this.isLoggingIn = false this.isInitializing = false this.reconnectAttempts = 0 this.lastLoginTime = 0 this.heartbeatFailCount = 0 } // ============== 初始化方法 ============== // 初始化腾讯IM async initTIM(userID = null) { if (this.isInitializing) { console.log('IM正在初始化中,跳过重复初始化') return false } this.isInitializing = true console.log('=== 开始初始化IM ===') try { // 重置重连次数,允许重新登录 this.reconnectAttempts = 0 // 如果存在旧的TIM实例,先完整清理 if (this.tim) { console.log('检测到旧的TIM实例,开始清理...') await this.cleanupOldInstance() } if (!TIM) { throw new Error('TIM SDK 未正确导入') } // 验证 SDKAppID if (!TIM_CONFIG.SDKAppID || isNaN(TIM_CONFIG.SDKAppID)) { throw new Error(`TIM SDK配置错误: SDKAppID无效 (${TIM_CONFIG.SDKAppID}),请检查环境变量 MP_TIM_SDK_APP_ID`) } console.log('创建TIM实例,SDKAppID:', TIM_CONFIG.SDKAppID) this.tim = TIM.create({ SDKAppID: TIM_CONFIG.SDKAppID }) // 等待TIM实例初始化完成 await this.waitForTIMInstanceReady() // 注册上传插件 this.tim.registerPlugin({ "tim-upload-plugin": TIMUploadPlugin }) console.log('上传插件已注册') // 注册事件监听器 this.registerEventListeners() // 设置日志级别 if (typeof this.tim.setLogLevel === 'function') { this.tim.setLogLevel(0) } // 获取用户信息并登录 await this.getUserInfoAndLogin(userID) // 等待SDK Ready console.log('等待SDK Ready...') await this.waitForSDKReady(IM_CONNECTION_CONFIG.SDK_READY_TIMEOUT) console.log('=== IM初始化完成 ===') return true } catch (error) { console.error('=== IM初始化失败 ===', error) this.triggerCallback('onError', `初始化失败: ${error.message || error}`) // 初始化失败时清理资源 await this.cleanupOldInstance() return false } finally { this.isInitializing = false } } // 清理旧的TIM实例 async cleanupOldInstance() { try { // 清理所有定时器 this.clearAllTimers() // 移除事件监听器 this.removeEventListeners() // 登出 if (this.tim && this.tim.isLoggedIn) { try { await this.tim.logout() console.log('已登出旧实例') } catch (err) { console.warn('登出旧实例失败:', err) } } // 清理实例 this.tim = null // 重置状态 this.resetAllStates() console.log('旧实例清理完成') } catch (error) { console.error('清理旧实例时出错:', error) } } // 等待TIM实例准备就绪 waitForTIMInstanceReady() { return new Promise((resolve, reject) => { const startTime = Date.now() const timeout = 10000 // 10秒超时 const checkReady = () => { if (this.tim && typeof this.tim.on === 'function') { console.log('TIM实例已就绪') resolve() } else if (Date.now() - startTime > timeout) { reject(new Error('等待TIM实例就绪超时')) } else { setTimeout(checkReady, IM_CONNECTION_CONFIG.TIM_INSTANCE_READY_CHECK_INTERVAL) } } checkReady() }) } // 等待SDK Ready(优化版:更好的超时处理) waitForSDKReady(timeout = IM_CONNECTION_CONFIG.SDK_READY_TIMEOUT) { return new Promise((resolve, reject) => { const startTime = Date.now() const checkInterval = 1000 // 每秒检查一次 let checkCount = 0 const checkSDKReady = () => { checkCount++ const elapsed = Date.now() - startTime if (this.isLoggedIn) { console.log(`✓ SDK已Ready(耗时${elapsed}ms,检查${checkCount}次)`) resolve() } else if (elapsed > timeout) { const error = new Error(`等待SDK Ready超时(${timeout}ms)`) console.error('✗', error.message) // 超时不算致命错误,尝试继续 resolve() } else { console.log(`等待SDK Ready... ${Math.floor(elapsed / 1000)}/${Math.floor(timeout / 1000)}秒`) setTimeout(checkSDKReady, checkInterval) } } // 立即开始第一次检查 checkSDKReady() }) } // 注册事件监听器 registerEventListeners() { if (!this.tim || !TIM.EVENT) return try { // 检查TIM实例是否已经初始化完成 if (!this.tim || typeof this.tim.on !== 'function') { console.log('TIM实例未准备好,延迟注册事件监听器') setTimeout(() => this.registerEventListeners(), 100) return } // 逐个注册事件监听器,如果某个注册失败则继续注册其他的 const events = [ { event: TIM.EVENT.SDK_READY, handler: this.boundEventHandlers.onSDKReady }, { event: TIM.EVENT.SDK_NOT_READY, handler: this.boundEventHandlers.onSDKNotReady }, { event: TIM.EVENT.MESSAGE_RECEIVED, handler: this.boundEventHandlers.onMessageReceived }, { event: TIM.EVENT.MESSAGE_SENT_SUCCEEDED, handler: this.boundEventHandlers.onMessageSentSucceeded }, { event: TIM.EVENT.MESSAGE_SENT_FAILED, handler: this.boundEventHandlers.onMessageSentFailed }, { event: TIM.EVENT.CONVERSATION_LIST_UPDATED, handler: this.boundEventHandlers.onConversationListUpdated }, { event: TIM.EVENT.NET_STATE_CHANGE, handler: this.boundEventHandlers.onNetStateChange }, { event: TIM.EVENT.KICKED_OUT, handler: this.boundEventHandlers.onKickedOut } ] events.forEach(({ event, handler }) => { if (event && handler && typeof this.tim.on === 'function') { try { this.tim.on(event, handler) } catch (error) { console.error(`注册事件监听器失败 ${event}:`, error) } } }) console.log('TIM事件监听器注册完成') } catch (error) { console.error('注册TIM事件监听器失败:', error) } } // 移除事件监听器 removeEventListeners() { if (!this.tim || !TIM.EVENT) return try { // 检查TIM实例是否还有off方法 if (typeof this.tim.off !== 'function') return // 逐个移除事件监听器,如果某个移除失败则继续移除其他的 const events = [ { event: TIM.EVENT.SDK_READY, handler: this.boundEventHandlers.onSDKReady }, { event: TIM.EVENT.SDK_NOT_READY, handler: this.boundEventHandlers.onSDKNotReady }, { event: TIM.EVENT.MESSAGE_RECEIVED, handler: this.boundEventHandlers.onMessageReceived }, { event: TIM.EVENT.MESSAGE_SENT_SUCCEEDED, handler: this.boundEventHandlers.onMessageSentSucceeded }, { event: TIM.EVENT.MESSAGE_SENT_FAILED, handler: this.boundEventHandlers.onMessageSentFailed }, { event: TIM.EVENT.CONVERSATION_LIST_UPDATED, handler: this.boundEventHandlers.onConversationListUpdated }, { event: TIM.EVENT.NET_STATE_CHANGE, handler: this.boundEventHandlers.onNetStateChange } ] events.forEach(({ event, handler }) => { if (event && handler && typeof this.tim.off === 'function') { try { this.tim.off(event, handler) } catch (error) { console.error(`移除事件监听器失败 ${event}:`, error) } } }) } catch (error) { console.error('移除TIM事件监听器失败:', error) } } // 获取用户信息并登录 async getUserInfoAndLogin(userID) { try { if (userID) { this.currentUserID = userID uni.setStorageSync('userInfo', { userID }) } else { const userInfo = uni.getStorageSync('userInfo') if (!userInfo?.userID) { throw new Error('未找到用户信息,请先登录') } this.currentUserID = userInfo.userID } this.currentUserSig = await this.getUserSig(this.currentUserID) await this.loginTIM() } catch (error) { console.error('获取用户信息失败:', error) this.triggerCallback('onError', `登录失败: ${error.message || error}`) throw error // 重新抛出错误,让调用者知道登录失败 } } // 获取 userSig async getUserSig(userID) { try { const response = await api('getUserSig', { userId: userID }) if (response?.success && response?.data) { return response.data } throw new Error('获取 userSig 失败: 接口返回数据格式错误') } catch (error) { console.error('获取UserSig失败:', error) throw error } } // 登录腾讯IM loginTIM() { return new Promise((resolve, reject) => { if (this.isLoggingIn) { reject(new Error('正在登录中')) return } const now = Date.now() if (now - this.lastLoginTime < this.loginCooldown) { reject(new Error('登录冷却中')) return } if (this.isLoggedIn) { resolve() return } this.isLoggingIn = true this.lastLoginTime = now console.log('开始登录腾讯IM,userID:', this.currentUserID) this.tim.login({ userID: this.currentUserID, userSig: this.currentUserSig }).then(() => { console.log('腾讯IM登录请求成功,等待SDK_READY事件...') this.isLoggingIn = false // 不在这里设置 isLoggedIn = true,等待 onSDKReady 事件 this.reconnectAttempts = 0 // 触发登录状态变化回调(登录中状态) this.triggerCallback('onLoginStatusChanged', { isLoggedIn: false, userID: this.currentUserID, reason: 'LOGIN_REQUESTED', message: '登录请求成功,等待SDK就绪...' }) resolve() }).catch(error => { console.error('腾讯IM登录失败:', error) this.isLoggingIn = false this.triggerCallback('onLoginStatusChanged', { isLoggedIn: false, error, reason: 'LOGIN_FAILED' }) reject(error) }) }) } // ============== 连接监控方法 ============== // 启动登录状态检测(优化版:使用配置常量) startLoginStatusCheck() { this.stopLoginStatusCheck() // 根据连接稳定性动态调整检查间隔 const getCheckInterval = () => { if (this.reconnectAttempts > 0) { return IM_CONNECTION_CONFIG.LOGIN_CHECK_INTERVAL_UNSTABLE } return IM_CONNECTION_CONFIG.LOGIN_CHECK_INTERVAL_STABLE } const checkStatus = () => { // 如果已达到最大重连次数(被踢下线),停止检查 if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('⚠️ 已被踢下线或达到最大重连次数,停止登录状态检查') return } // 如果正在登录中,跳过本次检查 if (this.isLoggingIn) { this.loginCheckInterval = setTimeout(checkStatus, getCheckInterval()) return } const isLoggedIn = this.checkLoginStatus() // 如果未登录且有用户ID,尝试重连 if (!isLoggedIn && this.currentUserID && !this.isLoggingIn) { console.log('📡 登录状态检测:发现未登录,尝试重连') this.attemptReconnect() } else if (isLoggedIn) { // 登录正常,重置重连计数 if (this.reconnectAttempts > 0) { console.log('✓ 登录状态检测:连接已恢复正常') this.reconnectAttempts = 0 } // 定期清理缓存 this.cleanupCache() } // 重新调度下一次检查(再次检查,防止在检查过程中被踢下线) if (this.reconnectAttempts < this.maxReconnectAttempts) { this.loginCheckInterval = setTimeout(checkStatus, getCheckInterval()) } } // 首次检查延迟 const firstDelay = IM_CONNECTION_CONFIG.LOGIN_CHECK_FIRST_DELAY this.loginCheckInterval = setTimeout(checkStatus, firstDelay) console.log(`🔍 登录状态检测已启动,首次检查将在${firstDelay / 1000}秒后进行`) } // 停止登录状态检测 stopLoginStatusCheck() { if (this.loginCheckInterval) { clearTimeout(this.loginCheckInterval) clearInterval(this.loginCheckInterval) this.loginCheckInterval = null } } // 检查登录状态 checkLoginStatus() { if (!this.tim) return false try { // 【修复】优先信任已设置的登录状态,只有在明确检测到未登录时才更新 // 如果已经标记为登录状态,不应该被轻易覆盖 if (this.isLoggedIn) { // 只在已登录状态下,通过 SDK 再次验证 let sdkLoginStatus = true // 默认保持登录状态 if (typeof this.tim.getLoginStatus === 'function') { try { const status = this.tim.getLoginStatus() // 只有明确检测到未登录状态才更新 if (status === TIM.TYPES.NOT_LOGGED_IN || status === TIM.TYPES.KICKED_OUT) { sdkLoginStatus = false console.warn('SDK 检测到未登录状态:', status) } } catch (e) { console.error('获取 SDK 登录状态失败:', e) // 出错时保持当前状态 } } // 只有 SDK 明确返回未登录时才更新状态 if (!sdkLoginStatus) { this.isLoggedIn = false this.triggerCallback('onLoginStatusChanged', { isLoggedIn: false, userID: this.currentUserID, reason: 'SDK_STATUS_CHECK' }) return false } // 保持登录状态 return true } // 如果当前是未登录状态,检查 SDK 是否已登录 let loginStatus = false if (typeof this.tim.isLoggedIn === 'boolean') { loginStatus = this.tim.isLoggedIn } else if (typeof this.tim.getLoginStatus === 'function') { const status = this.tim.getLoginStatus() loginStatus = status === TIM.TYPES.LOGINED || status === TIM.TYPES.LOGGING_IN } // 只有在检测到登录状态变化时才更新 if (loginStatus && !this.isLoggedIn) { this.isLoggedIn = true this.triggerCallback('onLoginStatusChanged', { isLoggedIn: true, userID: this.currentUserID, reason: 'SDK_STATUS_CHECK' }) } return loginStatus } catch (error) { console.error('检查IM登录状态失败:', error) // 出错时返回当前状态,不改变状态 return this.isLoggedIn } } // 尝试重新连接(优化版:更智能的重连策略) async attemptReconnect() { // 如果已达到最大重连次数(包括被踢下线的情况),停止重连 if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('⚠️ 已达到最大重连次数或被踢下线,停止自动重连') return false } if (this.isLoggingIn) { console.log('正在登录中,跳过重连') return false } // 先检查是否真的需要重连 if (this.tim && this.isLoggedIn) { console.log('IM已登录,无需重连') this.reconnectAttempts = 0 return true } const now = Date.now() const delayIndex = Math.min(this.reconnectAttempts, this.reconnectDelays.length - 1) const reconnectDelay = this.reconnectDelays[delayIndex] const timeSinceLastLogin = now - this.lastLoginTime // 只有非首次重连才需要检查冷却时间 if (this.reconnectAttempts > 0 && timeSinceLastLogin < reconnectDelay) { const remainingTime = reconnectDelay - timeSinceLastLogin console.log(`重连冷却中,剩余时间:${Math.ceil(remainingTime / 1000)}秒`) // 安排下次重连 setTimeout(() => { if (!this.isLoggedIn && !this.isLoggingIn && this.reconnectAttempts < this.maxReconnectAttempts) { this.attemptReconnect() } }, remainingTime) return false } if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('达到最大重连次数,停止自动重连') this.triggerCallback('onError', 'IM连接失败,请检查网络或手动刷新页面') return false } this.reconnectAttempts++ console.log(`第${this.reconnectAttempts}次重连尝试(最多${this.maxReconnectAttempts}次)`) try { if (this.tim && !this.isLoggedIn) { console.log('TIM实例存在但未登录,尝试登录...') await this.loginTIM() console.log('重连成功!') this.reconnectAttempts = 0 // 重置重连次数 return true } if (!this.tim) { console.log('TIM实例不存在,重新初始化...') await this.initTIM(this.currentUserID) console.log('重新初始化并登录成功!') this.reconnectAttempts = 0 return true } return false } catch (error) { console.error(`第${this.reconnectAttempts}次重连失败:`, error) // 如果还有重连机会,自动安排下一次重连 if (this.reconnectAttempts < this.maxReconnectAttempts) { const nextDelay = this.reconnectDelays[Math.min(this.reconnectAttempts, this.reconnectDelays.length - 1)] console.log(`${nextDelay / 1000}秒后进行第${this.reconnectAttempts + 1}次重连...`) setTimeout(() => { if (!this.isLoggedIn && !this.isLoggingIn && this.reconnectAttempts < this.maxReconnectAttempts) { this.attemptReconnect() } }, nextDelay) } return false } } // SDK Ready 事件 onSDKReady() { console.log('腾讯IM SDK Ready') this.isLoggedIn = true this.triggerCallback('onSDKReady') this.triggerCallback('onLoginStatusChanged', { isLoggedIn: true, reason: 'SDK_READY' }) this.startLoginStatusCheck() this.startHeartbeat() // 启动心跳检测 } // SDK Not Ready 事件 onSDKNotReady() { console.log('腾讯IM SDK Not Ready') this.isLoggedIn = false this.triggerCallback('onSDKNotReady') this.triggerCallback('onLoginStatusChanged', { isLoggedIn: false, reason: 'SDK_NOT_READY' }) this.stopLoginStatusCheck() this.stopHeartbeat() // 停止心跳检测 } // 收到新消息 onMessageReceived(event) { event.data.forEach(message => { const existingMessage = this.messageList.find(msg => msg.ID === message.ID) if (existingMessage) return if (!this.filterMessage(message)) return const convertedMessage = this.convertMessageFormat(message) if (!convertedMessage.conversationID) { convertedMessage.conversationID = message.conversationID } // 获取消息所属的会话ID const messageConversationID = convertedMessage.conversationID console.log('收到新消息:', { messageID: convertedMessage.ID, messageConversationID: messageConversationID, currentConversationID: this.currentConversationID, messageType: convertedMessage.type, from: convertedMessage.from }) // 判断是否为当前会话的消息(必须有currentConversationID且匹配才显示) const isCurrentConversation = this.currentConversationID && messageConversationID === this.currentConversationID console.log('消息会话匹配检查:', { isCurrentConversation, hasCurrentConversationID: !!this.currentConversationID, conversationIDMatch: messageConversationID === this.currentConversationID }) if (isCurrentConversation) { // 当前会话的消息,触发回调 console.log('✓ 消息属于当前会话,触发显示') this.triggerCallback('onMessageReceived', convertedMessage) // 处理已读状态 if (this.currentConversationID) { this.markConversationAsRead(this.currentConversationID) } this.triggerCallback('onConversationListUpdated', { reason: 'NEW_MESSAGE_RECEIVED_IN_CURRENT_CONVERSATION', conversation: { conversationID: messageConversationID || this.currentConversationID, lastMessage: this.formatLastMessage(convertedMessage), lastMessageTime: convertedMessage.lastTime, unreadCount: 0, messageType: convertedMessage.type, messageFlow: convertedMessage.flow }, message: convertedMessage }) } else { // 非当前会话的消息,只更新会话列表 console.log('✗ 消息不属于当前会话,已过滤不显示,仅更新会话列表', { messageConversationID, currentConversationID: this.currentConversationID }) this.updateConversationListOnNewMessage(convertedMessage) } }) } // 消息发送成功 onMessageSentSucceeded(event) { const messageID = event.data.ID const sentMessage = event.data.message // 更新messageList中的消息状态 const message = this.messageList.find(msg => msg.ID === messageID) if (message) { message.status = 'success' // 如果服务器返回了真实的消息ID,更新它 if (sentMessage && sentMessage.ID && sentMessage.ID !== messageID) { message.ID = sentMessage.ID } } // 缓存功能已移除 this.triggerCallback('onMessageSent', { messageID, status: 'success' }) } // 消息发送失败 onMessageSentFailed(event) { const messageID = event.data.ID // 更新messageList中的消息状态 const message = this.messageList.find(msg => msg.ID === messageID) if (message) { message.status = 'failed' } // 缓存功能已移除 this.triggerCallback('onMessageSent', { messageID, status: 'failed' }) this.triggerCallback('onError', `发送失败: ${event.data.error || '未知错误'}`) } // 会话列表更新 onConversationListUpdated(event) { this.triggerCallback('onConversationListUpdated', event.data) } // 网络状态变化(优化版:更稳定的处理) onNetStateChange(event) { const netState = event.data.netState console.log('🌐 网络状态变化:', netState) // 清理之前的网络重连定时器 if (this.networkReconnectTimer) { clearTimeout(this.networkReconnectTimer) this.networkReconnectTimer = null } if (netState === TIM.TYPES.NET_STATE_CONNECTED) { console.log('✓ 网络已连接,延迟检查IM状态以确保稳定') // 网络恢复后延迟再检查,避免网络还不稳定时立即重连 const delay = IM_CONNECTION_CONFIG.NETWORK_RECONNECT_DELAY this.networkReconnectTimer = setTimeout(() => { if (this.tim && !this.isLoggedIn && !this.isLoggingIn) { console.log('🔄 网络已稳定,开始重连') this.ensureIMConnection() } else if (this.isLoggedIn) { console.log('✓ 网络已稳定,IM连接正常') } this.networkReconnectTimer = null }, delay) // 重置重连次数(网络恢复后给更多机会) if (this.reconnectAttempts > 0) { console.log(`重置重连次数(之前: ${this.reconnectAttempts})`) this.reconnectAttempts = 0 } } else if (netState === TIM.TYPES.NET_STATE_CONNECTING) { console.log('🔗 网络连接中,等待稳定...') } else if (netState === TIM.TYPES.NET_STATE_DISCONNECTED) { console.log('⚠️ 网络断开(暂不标记为未登录,等待心跳检测判断)') // 网络断开时不立即设置 isLoggedIn = false // 因为可能只是短暂的网络波动,让心跳检测来判断是否真的断线 this.triggerCallback('onLoginStatusChanged', { isLoggedIn: this.isLoggedIn, reason: 'NETWORK_DISCONNECTED', message: '网络波动,将自动重连' }) } } // 被踢下线处理 onKickedOut(event) { console.log('⚠️ 账号被踢下线:', event.data) // 更新登录状态 this.isLoggedIn = false this.isLoggingIn = false // 停止所有定时器(防止自动重连) this.clearAllTimers() this.stopLoginStatusCheck() this.stopHeartbeat() // 清除重连相关状态,阻止重连 this.reconnectAttempts = this.maxReconnectAttempts // 设置为最大值,阻止重连 // 触发登录状态变化回调 this.triggerCallback('onLoginStatusChanged', { isLoggedIn: false, reason: 'KICKED_OUT', message: '您的账号在其他设备登录' }) // 显示提示弹框 uni.showModal({ title: '提示', content: '您的账号在其他设备登录,您已被踢下线', showCancel: false, confirmText: '确定', success: () => { // 清理本地缓存并跳转到登录页 uni.removeStorageSync('token') uni.removeStorageSync('refreshToken') uni.removeStorageSync('account') uni.removeStorageSync('openid') uni.reLaunch({ url: '/pages-center/login/login' }) } }) } // 手动检测并重连IM async ensureIMConnection() { if (this.isLoggingIn || !this.tim) return true const isLoggedIn = this.checkLoginStatus() if (!isLoggedIn && this.currentUserID) { return await this.attemptReconnect() } return true } startHeartbeat() { this.stopHeartbeat() // 心跳失败计数器 this.heartbeatFailCount = 0 const MAX_HEARTBEAT_FAIL = IM_CONNECTION_CONFIG.HEARTBEAT_MAX_FAIL const INTERVAL = IM_CONNECTION_CONFIG.HEARTBEAT_INTERVAL // 定时心跳检测 this.heartbeatInterval = setInterval(() => { // 如果已达到最大重连次数(被踢下线),停止心跳检测 if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('⚠️ 已被踢下线或达到最大重连次数,停止心跳检测') this.stopHeartbeat() return } // 只在已登录状态下进行心跳检测 if (!this.tim || !this.isLoggedIn) { console.log('⏸ 心跳检测:未登录,跳过检测') return } // 确保方法存在 if (typeof this.tim.getConversationList !== 'function') { console.log('⏸ 心跳检测:SDK方法不可用,跳过检测') return } this.tim.getConversationList() .then(() => { if (this.heartbeatFailCount > 0) { console.log(`� 心跳恢复正常(之前失败${this.heartbeatFailCount}次)`) } this.heartbeatFailCount = 0 // 重置失败计数 }) .catch((error) => { // 如果是SDK未就绪错误,不计入失败次数(这是临时状态) if (error && error.message && error.message.includes('sdk not ready')) { console.log('⏸ 心跳检测:SDK未就绪,跳过本次检测') return } this.heartbeatFailCount++ console.error(`💔 心跳失败 (${this.heartbeatFailCount}/${MAX_HEARTBEAT_FAIL}):`, error.message) // 只有连续失败多次才认为真的断线 if (this.heartbeatFailCount >= MAX_HEARTBEAT_FAIL) { console.log('❌ 心跳连续失败,标记为未登录并尝试重连') this.isLoggedIn = false this.heartbeatFailCount = 0 // 只有未被踢下线才尝试重连 if (this.reconnectAttempts < this.maxReconnectAttempts) { this.attemptReconnect() } } }) }, INTERVAL) console.log(`💓 心跳检测已启动(间隔${INTERVAL / 1000}秒,最多失败${MAX_HEARTBEAT_FAIL}次)`) } // 停止心跳检测 stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval) this.heartbeatInterval = null console.log('IM心跳检测已停止') } } // 获取会话列表 getConversationList() { if (!this.tim) { console.error('TIM实例不存在,无法获取会话列表') return } if (!this.isLoggedIn) { console.log('SDK未ready,等待SDK初始化后再获取会话列表...') // 等待SDK就绪 const checkSDKReady = () => { if (this.isLoggedIn) { console.log('SDK已ready,开始获取会话列表') this.getConversationList() } else { setTimeout(checkSDKReady, 500) } } setTimeout(checkSDKReady, 500) return } } // 获取群聊列表 getGroupList() { return new Promise((resolve, reject) => { if (!this.tim) { reject(new Error('TIM实例不存在')) return } if (!this.isLoggedIn) { console.log('SDK未ready,等待SDK初始化...') let waitTime = 0 const maxWaitTime = 30000 // 最多等待30秒 const checkInterval = 1000 // 每秒检查一次 let timeoutHandle = null const checkSDKReady = () => { if (this.isLoggedIn) { console.log('SDK已ready,开始获取群聊列表') if (timeoutHandle) clearTimeout(timeoutHandle) this.getGroupListInternal().then(resolve).catch(reject) } else if (waitTime >= maxWaitTime) { console.error('等待SDK就绪超时') if (timeoutHandle) clearTimeout(timeoutHandle) reject(new Error('SDK初始化超时,请检查网络连接')) } else { waitTime += checkInterval console.log(`等待SDK就绪... (${Math.floor(waitTime / 1000)}/${Math.floor(maxWaitTime / 1000)}秒)`) timeoutHandle = setTimeout(checkSDKReady, checkInterval) } } checkSDKReady() return } this.getGroupListInternal().then(resolve).catch(reject) }) } // 内部获取群聊列表方法 getGroupListInternal() { return new Promise((resolve, reject) => { console.log('开始获取群聊列表') // 直接调用,SDK就绪检查已在getGroupList()中完成 this.tim.getConversationList() .then(async (conversationResponse) => { console.log('获取会话列表成功') const groupConversations = conversationResponse.data.conversationList.filter(conversation => { return conversation.conversationID && conversation.conversationID.startsWith('GROUP') }) console.log('群聊会话列表数量:', groupConversations.length) // 先获取一次群组列表,避免在循环中重复调用 let allGroups = [] try { const groupListResponse = await this.tim.getGroupList() allGroups = groupListResponse.data.groupList || [] } catch (error) { console.error('获取群组列表失败:', error) } const groupsWithInfo = await Promise.all( groupConversations.map(async (conversation) => { try { const groupName = conversation.groupProfile?.name || '' const [doctorId, patientName] = groupName.split('|') const groupID = conversation.conversationID.replace('GROUP', '') // 从已获取的群组列表中查找 const group = allGroups.find(g => g.groupID === groupID) const groupInfo = { groupID: groupID, name: group?.name || '问诊群聊', avatar: '/static/home/avatar.svg', memberCount: group?.memberCount || 0 } const lastMessage = conversation.lastMessage let lastMessageText = '暂无消息' let lastMessageTime = Date.now() if (lastMessage) { if (lastMessage.type === 'TIMTextElem') { lastMessageText = lastMessage.payload.text } else if (lastMessage.type === 'TIMImageElem') { lastMessageText = '[图片]' } else if (lastMessage.type === 'TIMSoundElem') { lastMessageText = '[语音]' } else if (lastMessage.type === 'TIMCustomElem') { lastMessageText = lastMessage.payload.data || '[自定义消息]' } else { lastMessageText = '[未知消息类型]' } lastMessageTime = (lastMessage.lastTime || lastMessage.time || 0) * 1000 } return { ...groupInfo, groupID: groupID, doctorId, patientName, conversationID: conversation.conversationID, lastMessage: lastMessageText, lastMessageTime: lastMessageTime, unreadCount: conversation.unreadCount || 0 } } catch (error) { console.error(`处理群聊会话失败:`, error) return { groupID: conversation.conversationID, name: conversation.groupProfile?.name || '问诊群聊', doctorId: '', patientName: '', avatar: '/static/home/avatar.svg', lastMessage: '获取失败', lastMessageTime: Date.now(), unreadCount: conversation.unreadCount || 0, memberCount: 0 } } }) ) console.log('处理后的群聊列表数量:', groupsWithInfo.length) resolve({ success: true, groupList: groupsWithInfo, totalCount: groupsWithInfo.length, data: conversationResponse.data }) }) .catch((imError) => { console.error('获取会话列表失败:', imError) reject({ success: false, error: imError }) }) }) } // 创建问诊群聊 createGroup({ type, name, groupID, memberList, timeout = 30000, retryCount = 2 }) { return new Promise((resolve, reject) => { if (!this.tim) { reject(new Error('TIM实例不存在')) return } if (!this.isLoggedIn) { console.log('SDK未ready,等待SDK初始化...') const maxWaitTime = 30000 const startTime = Date.now() const checkSDKReady = () => { if (this.isLoggedIn) { console.log('SDK已ready,开始创建群聊') this.createGroupInternal({ type, name, groupID, memberList, timeout, retryCount }) .then(resolve) .catch(reject) } else if (Date.now() - startTime > maxWaitTime) { reject(new Error('等待SDK初始化超时')) } else { console.log('SDK仍未ready,继续等待...') setTimeout(checkSDKReady, 1000) } } checkSDKReady() return } this.createGroupInternal({ type, name, groupID, memberList, timeout, retryCount }) .then(resolve) .catch(reject) }) } // 内部创建群组方法 async createGroupInternal({ type, name, groupID, memberList, timeout, retryCount }) { let lastError = null for (let attempt = 0; attempt <= retryCount; attempt++) { try { console.log(`创建群聊尝试 ${attempt + 1}/${retryCount + 1}:`, { type, name, groupID, memberCount: memberList ? memberList.length : 0 }) const result = await this.createGroupWithTimeout({ type, name, groupID, memberList, timeout }) console.log(`创建群聊成功,尝试次数: ${attempt + 1}`) return result } catch (error) { lastError = error console.error(`第 ${attempt + 1} 次创建群聊失败:`, error) if (attempt === retryCount) break const retryDelay = Math.min(1000 * Math.pow(2, attempt), 5000) console.log(`等待 ${retryDelay}ms 后重试...`) await new Promise(resolve => setTimeout(resolve, retryDelay)) } } throw lastError } // 带超时的创建群组方法 createGroupWithTimeout({ type, name, groupID, memberList, timeout = 30000 }) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`创建群聊超时 (${timeout}ms)`)) }, timeout) if (!groupID) { clearTimeout(timeoutId) reject(new Error('群聊ID不能为空')) return } if (!name) { clearTimeout(timeoutId) reject(new Error('群聊名称不能为空')) return } let groupType = TIM.TYPES.GRP_WORK if (type === 'GRP_WORK') { groupType = TIM.TYPES.GRP_WORK } else if (type === 'GRP_PUBLIC') { groupType = TIM.TYPES.GRP_PUBLIC } else if (type === 'GRP_CHATROOM') { groupType = TIM.TYPES.GRP_CHATROOM } else if (type === 'GRP_AVCHATROOM') { groupType = TIM.TYPES.GRP_AVCHATROOM } else if (type === 'GRP_MEETING') { groupType = TIM.TYPES.GRP_MEETING } const createGroupParams = { type: groupType, name: name, groupID: groupID } if (memberList && memberList.length > 0) { createGroupParams.memberList = memberList } console.log('创建群组参数:', createGroupParams) this.tim.createGroup(createGroupParams).then(async (imResponse) => { clearTimeout(timeoutId) console.log('创建问诊群聊成功:', imResponse) try { await api('sendSystemMessage', { groupID, status: 'pending' }) console.log('pending系统消息发送成功') } catch (error) { console.error('pending系统消息发送失败:', error) } resolve({ success: true, groupID: groupID, data: imResponse.data }) }).catch((imError) => { clearTimeout(timeoutId) const enhancedError = { ...imError, originalError: imError, groupID: groupID, name: name, type: type, timestamp: new Date().toISOString() } console.error('创建问诊群聊失败:', enhancedError) const errorMessage = enhancedError.message || enhancedError.originalError?.message || '' if (errorMessage.includes('group id has been used') || errorMessage.includes('group id has been used!')) { console.log('群聊ID已存在,视为创建成功') resolve({ success: true, groupID: groupID, data: { groupID: groupID }, message: '群聊已存在' }) return } reject({ success: false, error: enhancedError, userMessage: this.formatErrorMessage(enhancedError) }) }) }) } // 格式化错误信息 formatErrorMessage(error) { if (!error) return '创建群聊失败,请稍后重试' const errorCode = error.code || (error.originalError && error.originalError.code) const errorMessage = error.message || (error.originalError && error.originalError.message) || '' if (errorMessage.includes('group id has been used') || errorMessage.includes('group id has been used!')) { return 'SUCCESS_GROUP_EXISTS' } switch (errorCode) { case 2801: return '网络连接超时,请检查网络连接后重试' case 2800: return '网络连接异常,请检查网络设置' case 2802: return '服务器连接失败,请稍后重试' case 2803: return '请求失败,请检查网络连接' case 2804: return '服务暂时不可用,请稍后重试' case 10004: return '群聊ID已存在,请使用其他ID' case 10007: return '群聊创建权限不足' case 10010: return '群聊数量已达上限' default: if (errorMessage.includes('超时') || errorMessage.includes('timeout')) { return '操作超时,请检查网络连接后重试' } else if (errorMessage.includes('网络') || errorMessage.includes('network')) { return '网络连接异常,请检查网络设置' } else if (errorMessage.includes('权限') || errorMessage.includes('permission')) { return '操作权限不足,请联系管理员' } else { return `创建群聊失败:${errorMessage}` } } } // 设置当前会话ID setConversationID(conversationID) { this.currentConversationID = conversationID console.log('设置当前会话ID:', conversationID) } // 进入会话 enterConversation(conversationID) { console.log("【enterConversation】进入会话:", conversationID) // 更新当前会话ID this.currentConversationID = conversationID // 清空当前消息列表 this.messageList = [] // 重置分页状态 this.nextReqMessageID = "" this.isCompleted = false this.isLoadingMore = false console.log(" 会话ID已更新,消息列表已清空,分页状态已重置") // 进入群聊会话,默认加载20条消息 this.enterGroupConversation(conversationID, 20) } // 进入群聊会话 async enterGroupConversation(groupID, count = 20) { console.log("【enterGroupConversation】进入群聊会话, groupID:", groupID, "count:", count) let conversationID = groupID let actualGroupID = groupID if (groupID.startsWith('GROUP')) { actualGroupID = groupID.replace('GROUP', '') conversationID = groupID } else { conversationID = `GROUP${groupID}` actualGroupID = groupID } console.log(" conversationID:", conversationID, "actualGroupID:", actualGroupID) // 清空消息列表,准备加载新会话的消息 this.messageList = [] // 确保设置当前会话ID,防止消息混淆 this.currentConversationID = conversationID console.log('进入群聊会话,设置currentConversationID:', conversationID) this.conversation = { conversationID: conversationID, conversationType: TIM.TYPES.CONV_GROUP, groupProfile: { groupID: actualGroupID, name: '问诊群聊', type: TIM.TYPES.GRP_WORK } } this.markConversationAsRead(conversationID) // 首先从本地接口加载聊天记录 console.log(" 开始从本地接口加载聊天记录,groupID:", actualGroupID) await this.loadMessagesFromLocalAPI(actualGroupID, count) } // 从本地API加载聊天记录 async loadMessagesFromLocalAPI(groupID, count = 20, skip = 0, isPullUp = false) { try { console.log("【loadMessagesFromLocalAPI】开始从本地API加载聊天记录") console.log(" groupID:", groupID, "count:", count, "skip:", skip, "isPullUp:", isPullUp) // 调用本地接口获取聊天记录 const response = await api('getChatRecordsByGroupId', { groupID, count, skip }) if (response && response.success && response.data && response.data.records) { const dbMessages = response.data.records const hasMore = response.data.hasMore || false // 从后端获取hasMore字段 const total = response.data.total || 0 console.log(` 成功获取 ${dbMessages.length} 条聊天记录`) console.log(` hasMore: ${hasMore}, total: ${total}`) // 将数据库消息转换为IM消息格式 const convertedMessages = dbMessages .map(dbMsg => this.convertDBMessageToIMFormat(dbMsg)) .filter(msg => msg !== null && this.filterMessage(msg)) console.log(` 转换后 ${convertedMessages.length} 条消息`) // 按时间排序(从早到晚) convertedMessages.sort((a, b) => a.lastTime - b.lastTime) // 根据是否为上拉加载,决定如何更新消息列表 if (isPullUp) { // 上拉加载更多:将新消息插入到列表前面(历史消息) this.messageList.unshift(...convertedMessages) // 再次排序确保顺序正确 this.messageList.sort((a, b) => a.lastTime - b.lastTime) } else { // 首次加载:直接替换消息列表 this.messageList = convertedMessages } // 设置分页状态 - 使用后端返回的 hasMore 字段 this.isCompleted = !hasMore console.log(` 分页状态: isCompleted=${this.isCompleted}, hasMore=${hasMore}`) // 触发回调 this.triggerCallback("onMessageListLoaded", { messages: this.messageList, isPullUp: isPullUp, hasMore: hasMore, isCompleted: this.isCompleted, total: total, }) return { success: true, count: convertedMessages.length, hasMore: hasMore, isCompleted: this.isCompleted, messages: this.messageList, total: total, } } else { console.warn(" ⚠️ 本地API返回数据格式错误") return { success: false, message: "本地API返回数据格式错误", count: 0, } } } catch (error) { console.error(" ❌ 从本地API加载聊天记录失败:", error) return { success: false, error: error, message: "加载聊天记录失败", count: 0, } } } // 将数据库消息转换为IM消息格式 convertDBMessageToIMFormat(dbMsg) { try { if (!dbMsg || !dbMsg.MsgBody || !Array.isArray(dbMsg.MsgBody) || dbMsg.MsgBody.length === 0) { return null } // 获取第一个消息体(通常一个消息只有一个消息体) const msgBody = dbMsg.MsgBody[0] const msgType = msgBody.MsgType // 确定消息的流向(in/out) // 根据 From_Account 判断是否为当前用户发送的消息 const flow = dbMsg.From_Account === this.currentUserID ? 'out' : 'in' // 计算时间戳(毫秒) let lastTime = Date.now() if (dbMsg.MsgTime) { // MsgTime 是秒级时间戳 lastTime = dbMsg.MsgTime * 1000 } else if (dbMsg.createdAt) { lastTime = new Date(dbMsg.createdAt).getTime() } // 构建基础消息对象 const message = { ID: dbMsg.MsgSeq || dbMsg._id || `db_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, from: dbMsg.From_Account || '', flow: flow, type: msgType, payload: this.convertDBPayloadToIMPayload(msgType, msgBody.MsgContent), lastTime: lastTime, status: 'success', avatar: flow === 'in' ? '/static/home/avatar.svg' : '/static/center/user-avatar.png', conversationID: this.currentConversationID, MsgSeq: dbMsg.MsgSeq, // 保留 MsgSeq 用于分页 } return message } catch (error) { console.error("转换数据库消息格式失败:", error, dbMsg) return null } } // 将数据库消息体转换为IM payload格式 convertDBPayloadToIMPayload(msgType, msgContent) { try { if (!msgContent) { return {} } switch (msgType) { case 'TIMTextElem': return { text: msgContent.Text || '' } case 'TIMImageElem': return { imageInfoArray: msgContent.ImageInfoArray || [] } case 'TIMSoundElem': return { url: msgContent.Url || '', second: msgContent.Second || 0, downloadFlag: msgContent.Download_Flag || 2 } case 'TIMCustomElem': return { data: msgContent.Data || '', description: msgContent.Desc || '', extension: msgContent.Ext || '' } case 'TIMVideoFileElem': return { videoUrl: msgContent.VideoUrl || '', videoSize: msgContent.VideoSize || 0, videoSecond: msgContent.VideoSecond || 0, videoFormat: msgContent.VideoFormat || '', videoDownloadFlag: msgContent.VideoDownloadFlag || 2, thumbUrl: msgContent.ThumbUrl || '', thumbSize: msgContent.ThumbSize || 0, thumbWidth: msgContent.ThumbWidth || 0, thumbHeight: msgContent.ThumbHeight || 0, thumbFormat: msgContent.ThumbFormat || '', thumbDownloadFlag: msgContent.ThumbDownloadFlag || 2 } case 'TIMFileElem': return { url: msgContent.Url || '', fileSize: msgContent.FileSize || 0, fileName: msgContent.FileName || '', downloadFlag: msgContent.Download_Flag || 2 } default: return msgContent } } catch (error) { console.error("转换消息体失败:", error, msgType, msgContent) return {} } } // 分页加载消息(单次加载) async loadMessagesPage(options = {}) { if (!this.conversation) { console.log("会话未初始化,无法加载消息") return Promise.reject(new Error("会话未初始化")) } const count = options.count || 20 // 记录调用前是否已有nextReqMessageID,用于判断是否为首次加载 const isLoadMore = !!(this.nextReqMessageID && this.nextReqMessageID !== "") try { // 构建请求参数 const requestParams = { conversationID: this.conversation.conversationID, count: Math.min(count, 15), // 腾讯IM SDK限制count在1-15之间 } // 如果有nextReqMessageID,说明是加载更多 if (isLoadMore) { requestParams.nextReqMessageID = this.nextReqMessageID console.log("加载更多消息,nextReqMessageID:", this.nextReqMessageID) } else { console.log("首次加载消息") } console.log(" 📡 发送请求到腾讯IM:", requestParams) const response = await this.tim.getMessageList(requestParams) const messageList = response.data.messageList const oldNextReqMessageID = this.nextReqMessageID const oldIsCompleted = this.isCompleted this.nextReqMessageID = response.data.nextReqMessageID this.isCompleted = response.data.isCompleted console.log(" 📥 收到腾讯IM响应:") console.log(` 消息数量: ${messageList.length}`) console.log(` nextReqMessageID: ${oldNextReqMessageID} → ${this.nextReqMessageID}`) console.log(` isCompleted: ${oldIsCompleted} → ${this.isCompleted}`) if (messageList && messageList.length > 0) { // 过滤掉不需要显示的消息 const filteredMessages = messageList.filter((message) => this.filterMessage(message) ) // 转换消息格式 const messages = filteredMessages.map((message) => { return this.convertMessageFormat(message) }) // 将新消息插入到列表前面(历史消息在前) this.messageList.unshift(...messages) // 按时间戳排序消息列表(从早到晚) this.messageList.sort((a, b) => a.lastTime - b.lastTime) } // 触发回调 - 使用 isLoadMore 来判断是否为上拉加载 this.triggerCallback("onMessageListLoaded", { messages: this.messageList, isPullUp: isLoadMore, // 只有在加载更多时才是 true hasMore: !this.isCompleted, isCompleted: this.isCompleted, }) return { success: true, count: messageList ? messageList.length : 0, hasMore: !this.isCompleted, isCompleted: this.isCompleted, messages: this.messageList, } } catch (error) { console.error("分页加载消息失败:", error) return { success: false, error: error, count: 0, } } } // 加载更多消息(供页面调用) async loadMoreMessages() { console.log("【loadMoreMessages】开始加载更多消息") console.log(" 当前状态: isLoadingMore=", this.isLoadingMore) console.log(" 当前状态: isCompleted=", this.isCompleted) console.log(" 当前消息数量:", this.messageList.length) if (this.isLoadingMore) { console.log(" ⚠️ 正在加载中,跳过重复请求") return { success: false, message: "正在加载中" } } if (this.isCompleted) { console.log(" ✅ 已加载全部消息") return { success: false, message: "已加载全部消息" } } this.isLoadingMore = true console.log(" 🔄 设置 isLoadingMore = true") try { // 从本地数据库加载更多历史消息 // 使用当前消息列表的长度作为 skip 值 const skip = this.messageList.length // 获取群组ID let groupID = '' if (this.conversation && this.conversation.groupProfile) { groupID = this.conversation.groupProfile.groupID } else if (this.currentConversationID) { groupID = this.currentConversationID.replace('GROUP', '') } if (!groupID) { console.error(" ❌ 无法获取群组ID") this.isLoadingMore = false return { success: false, message: "无法获取群组ID", } } console.log(" 从本地数据库加载更多,groupID:", groupID, "skip:", skip) // 调用本地API加载更多消息 const result = await this.loadMessagesFromLocalAPI(groupID, 20, skip, true) this.isLoadingMore = false console.log(" 🔄 设置 isLoadingMore = false") console.log(" 📥 loadMessagesFromLocalAPI 返回结果:", result) if (result.success) { console.log(" ✅ 加载成功,新增", result.count, "条消息") return { success: true, count: result.count, hasMore: result.hasMore, isCompleted: result.isCompleted, } } else { console.log(" ❌ 加载失败:", result.message || result.error) return { success: false, error: result.error, message: result.message || "加载失败", } } } catch (error) { console.error(" ❌ 加载更多异常:", error) this.isLoadingMore = false return { success: false, error: error, message: "加载失败", } } } // 后台刷新服务器消息(已弃用缓存功能) async refreshMessagesFromServer(conversationID, cachedMessages) { try { const result = await this.getMessages({ getAllMessages: true }) if (result.success) { // 严格过滤:只保留属于当前会话的消息 const filteredMessages = result.messages.filter(msg => msg.conversationID === conversationID) this.messageList = filteredMessages console.log(`消息刷新完成,过滤后${filteredMessages.length}条消息`) // 触发更新回调 this.triggerCallback('onMessageListLoaded', { messages: this.messageList, isPullUp: false, hasMore: false, isCompleted: true, isRefresh: true // 标记这是刷新操作 }) } } catch (error) { console.error('后台刷新消息失败:', error) } } // 合并消息(保留本地消息,避免覆盖) mergeMessages(serverMessages, cachedMessages) { const messageMap = new Map() // 先添加服务器消息 serverMessages.forEach(msg => { if (msg.ID) { messageMap.set(msg.ID, msg) } }) // 再添加缓存消息(覆盖服务器消息,保留本地最新状态) cachedMessages.forEach(msg => { if (msg.ID) { // 如果是本地消息(ID以local_开头)或者是发送中/失败的消息,保留缓存中的版本 if (msg.ID.startsWith('local_') || msg.status === 'sending' || msg.status === 'failed') { messageMap.set(msg.ID, msg) } else if (!messageMap.has(msg.ID)) { // 服务器没有的消息也保留 messageMap.set(msg.ID, msg) } } }) // 转换为数组并按时间排序 const mergedArray = Array.from(messageMap.values()) mergedArray.sort((a, b) => a.lastTime - b.lastTime) console.log(`消息合并完成: 服务器${serverMessages.length}条, 缓存${cachedMessages.length}条, 合并后${mergedArray.length}条`) return mergedArray } // 获取消息列表 async getMessages(options = {}) { if (!this.conversation) { console.log('会话未初始化,跳过获取消息列表') this.isLoading = false return Promise.resolve({ success: false, error: '会话未初始化' }) } this.isLoading = true const defaultOptions = { count: 20, nextReqMessageID: '', isPullUp: false, getAllMessages: false } const requestOptions = { ...defaultOptions, ...options } try { if (requestOptions.getAllMessages) { return await this.getAllMessages(requestOptions) } else { return await this.getMessagesBatch(requestOptions) } } catch (error) { console.error('获取消息列表失败:', error) this.isLoading = false this.messageList = [] this.triggerCallback('onMessageListLoaded', { messages: this.messageList, isPullUp: requestOptions.isPullUp, hasMore: false, error: error }) return { success: false, error } } } // 获取单批消息 async getMessagesBatch(options) { const requestParams = { conversationID: this.conversation.conversationID, count: options.count } if (options.nextReqMessageID) { requestParams.nextReqMessageID = options.nextReqMessageID } console.log('请求消息列表参数:', requestParams) const response = await this.tim.getMessageList(requestParams) const messageList = response.data.messageList const nextReqMessageID = response.data.nextReqMessageID const isCompleted = response.data.isCompleted console.log(`TIM返回消息: ${messageList.length}条, isCompleted: ${isCompleted}`) // 处理消息 const messages = this.processMessages(messageList) console.log(`处理后消息: ${messages.length}条`) // 更新消息列表 if (options.isPullUp) { this.messageList.unshift(...messages) } else { this.messageList = messages } // 缓存消息 this.cacheMessages() this.isLoading = false // 触发回调(仅在非getAllMessages调用时触发) if (!options.skipCallback) { this.triggerCallback('onMessageListLoaded', { messages: this.messageList, isPullUp: options.isPullUp, hasMore: !isCompleted, nextReqMessageID: nextReqMessageID, isCompleted: isCompleted }) } // 返回当前批次的消息,而不是整个messageList return { success: true, messages: messages, // 返回处理后的当前批次消息 hasMore: !isCompleted, nextReqMessageID: nextReqMessageID, isCompleted: isCompleted } } // 获取全部历史消息 async getAllMessages(options) { console.log('开始获取全部历史消息...') let allMessages = [] let nextReqMessageID = options.nextReqMessageID || '' let isCompleted = false let requestCount = 0 const maxRequests = 50 // 最多请求50次,避免无限循环 while (!isCompleted && requestCount < maxRequests) { requestCount++ console.log(`获取历史消息第${requestCount}批...`) const batchOptions = { ...options, nextReqMessageID: nextReqMessageID, isPullUp: false, // 改为false,因为我们是在构建完整列表 skipCallback: true // 跳过单批回调,最后统一回调 } const result = await this.getMessagesBatch(batchOptions) if (!result.success) { console.error(`第${requestCount}批消息获取失败:`, result.error) throw new Error(result.error) } console.log(`第${requestCount}批获取到${result.messages.length}条消息`) // 将当前批次的消息添加到开头(历史消息在前) if (result.messages.length > 0) { // 使用Set去重,避免重复消息 const existingIds = new Set(allMessages.map(m => m.ID)) const newMessages = result.messages.filter(m => !existingIds.has(m.ID)) if (newMessages.length > 0) { allMessages.unshift(...newMessages) console.log(`添加${newMessages.length}条新消息,当前总数: ${allMessages.length}`) } } nextReqMessageID = result.nextReqMessageID isCompleted = result.isCompleted console.log(`isCompleted: ${isCompleted}, nextReqMessageID: ${nextReqMessageID}`) // 如果没有更多消息ID,说明已经获取完毕 if (!nextReqMessageID) { console.log('没有更多消息ID,获取完成') break } } if (requestCount >= maxRequests) { console.warn(`达到最大请求次数限制(${maxRequests}),停止获取`) } // 更新消息列表 this.messageList = allMessages this.cacheMessages() this.isLoading = false console.log(`全部历史消息获取完成,共${allMessages.length}条消息,请求了${requestCount}批`) // 触发回调 this.triggerCallback('onMessageListLoaded', { messages: this.messageList, isPullUp: false, hasMore: false, nextReqMessageID: '', isCompleted: true }) return { success: true, messages: this.messageList, hasMore: false, nextReqMessageID: '', isCompleted: true } } // 处理消息 - 统一过滤和转换 processMessages(messageList) { const filteredMessages = messageList.filter(message => this.filterMessage(message)) return filteredMessages.map(message => this.convertMessageFormat(message)) } // 缓存消息 - 统一缓存逻辑 cacheMessages() { // 缓存功能已移除 } // 清理指定会话的缓存(已弃用) clearConversationCache(conversationID) { // 缓存功能已移除 } // 清理所有缓存(已弃用) clearAllCache() { // 缓存功能已移除 } // 获取消息列表(兼容旧方法) getMessageList(options = {}) { return this.getMessages(options) } // 发送文本消息 async sendTextMessage(text, cloudCustomData = '') { if (!text.trim()) return if (!this.tim) { this.triggerCallback('onError', 'IM未初始化') return { success: false, error: 'IM未初始化' } } // 检查登录状态 if (!this.isLoggedIn) { console.error('IM未登录,无法发送消息'); this.triggerCallback('onError', 'IM未登录,请稍后重试') return { success: false, error: 'IM未登录' } } // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取 let conversationID = this.currentConversationID; if (!conversationID && this.conversation) { conversationID = this.conversation.conversationID; } if (!conversationID) { console.error('会话ID不存在'); this.triggerCallback('onError', '会话不存在,请重新进入聊天') return { success: false, error: '会话ID不存在' } } // 从 conversationID 提取 groupID let groupID = null; if (conversationID.startsWith('GROUP')) { groupID = conversationID.replace('GROUP', ''); } else if (this.conversation?.groupProfile?.groupID) { groupID = this.conversation.groupProfile.groupID; } if (!groupID) { console.error('无法获取群聊ID,conversationID:', conversationID); this.triggerCallback('onError', '无法获取群聊ID') return { success: false, error: '无法获取群聊ID' } } const localMessage = { ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, flow: 'out', type: 'TIMTextElem', payload: { text: text }, lastTime: Date.now(), status: 'sending', avatar: '/static/center/user-avatar.png', conversationID: conversationID, from: this.currentUserID } // 缓存功能已移除 this.triggerCallback('onMessageReceived', localMessage) const message = this.tim.createTextMessage({ to: groupID, conversationType: TIM.TYPES.CONV_GROUP, cloudCustomData, payload: { text: text } }) try { await this.tim.sendMessage(message) localMessage.status = 'success' return { success: true, message: localMessage } } catch (error) { console.error('文本消息发送失败:', error) localMessage.status = 'failed' // 如果是因为未登录导致的失败,尝试重连 if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) { console.log('检测到未登录错误,尝试重连...'); this.isLoggedIn = false; this.ensureIMConnection(); } return { success: false, error } } } // 发送图片消息 async sendImageMessage(imageFile) { console.log('sendImageMessage 被调用,参数:', imageFile); if (!this.tim) { this.triggerCallback('onError', 'IM未初始化') return { success: false, error: 'IM未初始化' } } // 检查登录状态 if (!this.isLoggedIn) { console.error('IM未登录,无法发送消息'); this.triggerCallback('onError', 'IM未登录,请稍后重试') return { success: false, error: 'IM未登录' } } // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取 let conversationID = this.currentConversationID; if (!conversationID && this.conversation) { conversationID = this.conversation.conversationID; } if (!conversationID) { console.error('会话ID不存在'); this.triggerCallback('onError', '会话不存在,请重新进入聊天') return { success: false, error: '会话ID不存在' } } // 从 conversationID 提取 groupID let groupID = null; if (conversationID.startsWith('GROUP')) { groupID = conversationID.replace('GROUP', ''); } else if (this.conversation?.groupProfile?.groupID) { groupID = this.conversation.groupProfile.groupID; } if (!groupID) { console.error('无法获取群聊ID,conversationID:', conversationID); this.triggerCallback('onError', '无法获取群聊ID') return { success: false, error: '无法获取群聊ID' } } console.log('发送图片消息,conversationID:', conversationID, 'groupID:', groupID); // imageFile 现在是完整的 wx.chooseImage 返回对象 console.log('接收到的图片选择结果:', imageFile); console.log('类型:', typeof imageFile); console.log('keys:', imageFile ? Object.keys(imageFile) : 'null'); // 验证对象 if (!imageFile) { console.error('图片选择结果为空'); this.triggerCallback('onError', '图片文件无效'); return { success: false, error: '图片选择结果为空' }; } // 获取文件路径用于显示预览 let previewPath = ''; if (imageFile.tempFilePaths && imageFile.tempFilePaths.length > 0) { previewPath = imageFile.tempFilePaths[0]; } else if (imageFile.tempFiles && imageFile.tempFiles.length > 0) { previewPath = imageFile.tempFiles[0].tempFilePath || imageFile.tempFiles[0].path; } console.log('预览路径:', previewPath); // 获取图片尺寸信息(用于本地预览) const imageInfo = await this.getImageInfo(previewPath); const localMessage = { ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, flow: 'out', type: 'TIMImageElem', payload: { imageInfoArray: [{ url: previewPath, width: imageInfo.width, height: imageInfo.height }] }, lastTime: Date.now(), status: 'sending', avatar: '/static/center/user-avatar.png', conversationID: conversationID, from: this.currentUserID } console.log('创建本地图片消息:', localMessage) // 缓存功能已移除 // 触发消息接收回调,让UI立即显示 this.triggerCallback('onMessageReceived', localMessage) console.log('准备创建 TIM 图片消息,groupID:', groupID, 'imageFile:', imageFile); try { // 创建图片消息 - 直接传递 wx.chooseImage 的完整返回对象 const message = this.tim.createImageMessage({ to: groupID, conversationType: TIM.TYPES.CONV_GROUP, payload: { file: imageFile // 传递完整的 wx.chooseImage 返回对象 } }) console.log('TIM 图片消息已创建:', message); console.log('开始发送图片消息...'); const sendResult = await this.tim.sendMessage(message); console.log('图片消息发送成功:', sendResult); localMessage.status = 'success' return { success: true, message: localMessage } } catch (error) { console.error('图片消息发送失败:', error) console.error('错误详情:', { message: error.message, stack: error.stack, imageFile: imageFile }); localMessage.status = 'failed' // 如果是因为未登录导致的失败,尝试重连 if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) { console.log('检测到未登录错误,尝试重连...'); this.isLoggedIn = false; this.ensureIMConnection(); } return { success: false, error } } } // 发送语音消息 async sendVoiceMessage(voiceFile, duration) { if (!this.tim) { this.triggerCallback('onError', 'IM未初始化') return { success: false, error: 'IM未初始化' } } // 检查登录状态 if (!this.isLoggedIn) { console.error('IM未登录,无法发送消息'); this.triggerCallback('onError', 'IM未登录,请稍后重试') return { success: false, error: 'IM未登录' } } // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取 let conversationID = this.currentConversationID; if (!conversationID && this.conversation) { conversationID = this.conversation.conversationID; } if (!conversationID) { console.error('会话ID不存在'); this.triggerCallback('onError', '会话不存在,请重新进入聊天') return { success: false, error: '会话ID不存在' } } // 从 conversationID 提取 groupID let groupID = null; if (conversationID.startsWith('GROUP')) { groupID = conversationID.replace('GROUP', ''); } else if (this.conversation?.groupProfile?.groupID) { groupID = this.conversation.groupProfile.groupID; } if (!groupID) { console.error('无法获取群聊ID,conversationID:', conversationID); this.triggerCallback('onError', '无法获取群聊ID') return { success: false, error: '无法获取群聊ID' } } const localMessage = { ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, flow: 'out', type: 'TIMSoundElem', payload: { url: this.getVoiceUrl(voiceFile), second: duration }, lastTime: Date.now(), status: 'sending', avatar: '/static/center/user-avatar.png', conversationID: conversationID, from: this.currentUserID } // 缓存功能已移除 this.triggerCallback('onMessageReceived', localMessage) const message = this.tim.createAudioMessage({ to: groupID, conversationType: TIM.TYPES.CONV_GROUP, payload: { file: voiceFile } }) try { await this.tim.sendMessage(message) localMessage.status = 'success' return { success: true, message: localMessage } } catch (error) { console.error('语音消息发送失败:', error) localMessage.status = 'failed' // 如果是因为未登录导致的失败,尝试重连 if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) { console.log('检测到未登录错误,尝试重连...'); this.isLoggedIn = false; this.ensureIMConnection(); } return { success: false, error } } } // 发送自定义消息 async sendCustomMessage(messageData) { if (!this.tim) { this.triggerCallback('onError', 'IM未初始化') return { success: false, error: 'IM未初始化' } } // 检查登录状态 if (!this.isLoggedIn) { console.error('IM未登录,无法发送消息'); this.triggerCallback('onError', 'IM未登录,请稍后重试') return { success: false, error: 'IM未登录' } } // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取 let conversationID = this.currentConversationID; if (!conversationID && this.conversation) { conversationID = this.conversation.conversationID; } if (!conversationID) { console.error('会话ID不存在'); this.triggerCallback('onError', '会话不存在,请重新进入聊天') return { success: false, error: '会话ID不存在' } } // 从 conversationID 提取 groupID let groupID = null; if (conversationID.startsWith('GROUP')) { groupID = conversationID.replace('GROUP', ''); } else if (this.conversation?.groupProfile?.groupID) { groupID = this.conversation.groupProfile.groupID; } if (!groupID) { console.error('无法获取群聊ID,conversationID:', conversationID); this.triggerCallback('onError', '无法获取群聊ID') return { success: false, error: '无法获取群聊ID' } } const localMessage = { ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, flow: 'out', type: 'TIMCustomElem', payload: { data: JSON.stringify(messageData), description: messageData.content || '自定义消息', extension: messageData.messageType || 'custom' }, lastTime: Date.now(), status: 'sending', avatar: '/static/center/user-avatar.png', conversationID: conversationID, from: this.currentUserID } // 缓存功能已移除 this.triggerCallback('onMessageReceived', localMessage) const message = this.tim.createCustomMessage({ to: groupID, conversationType: TIM.TYPES.CONV_GROUP, payload: { data: JSON.stringify(messageData), description: messageData.content || '自定义消息', extension: messageData.messageType || 'custom' } }) try { await this.tim.sendMessage(message) localMessage.status = 'success' return { success: true, message: localMessage } } catch (error) { console.error('自定义消息发送失败:', error) localMessage.status = 'failed' // 如果是因为未登录导致的失败,尝试重连 if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) { console.log('检测到未登录错误,尝试重连...'); this.isLoggedIn = false; this.ensureIMConnection(); } return { success: false, error } } } // 工具方法 filterMessage(message) { if (message.type === 'TIMCustomElem' && message.payload && message.payload.data) { if (message.payload.data === 'group_create' || message.payload.data === 'purchased') { console.log('过滤消息:', message.ID, message.payload.data) return false } } return true } convertMessageFormat(timMessage) { let lastTime if (timMessage.lastTime) { lastTime = timMessage.lastTime * 1000 } else if (timMessage.time) { lastTime = timMessage.time * 1000 } else if (timMessage.timestamp) { lastTime = timMessage.timestamp * 1000 } else { lastTime = Date.now() } const message = { ID: timMessage.ID, from: timMessage.from, flow: timMessage.flow, type: timMessage.type, payload: timMessage.payload, lastTime: lastTime, status: timMessage.status || 'success', avatar: timMessage.flow === 'in' ? '/static/home/avatar.svg' : '/static/center/user-avatar.png', // 优先使用消息本身的conversationID,确保消息归属正确的会话 conversationID: timMessage.conversationID } return message } formatLastMessage(message) { try { switch (message.type) { case 'TIMTextElem': return message.payload.text || '[文本消息]' case 'TIMImageElem': return '[图片]' case 'TIMSoundElem': return '[语音]' case 'TIMCustomElem': try { const customData = JSON.parse(message.payload.data) if (customData.messageType === 'symptom') { return '[病情描述]' } else if (customData.messageType === 'prescription') { return '[处方单]' } else if (customData.messageType === 'refill') { return '[续方申请]' } else if (customData.messageType === 'survey') { return '[问卷调查]' } else { return customData.content || '[自定义消息]' } } catch (error) { return '[自定义消息]' } default: return '[未知消息类型]' } } catch (error) { console.error('格式化最后一条消息失败:', error) return '[消息]' } } getImageUrl(imageFile) { // 支持 tempFilePath 或 path if (imageFile?.tempFilePath) { return imageFile.tempFilePath; } if (imageFile?.path) { return imageFile.path; } // 处理字符串路径 if (typeof imageFile === 'string') { return imageFile; } console.warn('无法获取图片URL,使用默认图片:', imageFile); return '/static/home/photo.png'; } // 获取图片尺寸信息 getImageInfo(imagePath) { return new Promise((resolve) => { // 如果传入的是对象,尝试提取路径 if (typeof imagePath === 'object') { if (imagePath?.tempFilePath) { imagePath = imagePath.tempFilePath; } else if (imagePath?.path) { imagePath = imagePath.path; } else { console.warn('无法从对象中获取图片路径,使用默认尺寸:', imagePath); resolve({ width: 400, height: 300 }); return; } } // 如果不是字符串,使用默认尺寸 if (typeof imagePath !== 'string' || !imagePath) { console.warn('图片路径无效,使用默认尺寸:', imagePath); resolve({ width: 400, height: 300 }); return; } console.log('获取图片信息,路径:', imagePath); // 使用uni.getImageInfo获取图片尺寸 uni.getImageInfo({ src: imagePath, success: (res) => { console.log('获取图片尺寸成功:', res); resolve({ width: res.width, height: res.height }); }, fail: (err) => { console.error('获取图片尺寸失败:', err); // 失败时使用默认尺寸 resolve({ width: 400, height: 300 }); } }); }); } getVoiceUrl(voiceFile) { if (typeof voiceFile === 'string') { return voiceFile } if (voiceFile && voiceFile.tempFilePath) { return voiceFile.tempFilePath } if (voiceFile && voiceFile.filePath) { return voiceFile.filePath } return '/static/voice/default.mp3' } // 标记会话为已读 markConversationAsRead(conversationID) { if (!this.tim || !this.isLoggedIn) return try { let formattedConversationID = conversationID if (!conversationID.startsWith('GROUP')) { formattedConversationID = `GROUP${conversationID}` } this.tim.setMessageRead({ conversationID: formattedConversationID }).then(() => { this.triggerCallback('onConversationListUpdated', { conversationID: formattedConversationID, unreadCount: 0 }) }).catch(error => { console.error('标记会话已读失败:', error) }) } catch (error) { console.error('标记会话已读异常:', error) } } // 更新会话列表 updateConversationListOnNewMessage(message) { try { const conversationID = message.conversationID || this.currentConversationID if (!conversationID) return const conversationUpdate = { conversationID: conversationID, lastMessage: this.formatLastMessage(message), lastMessageTime: message.lastTime, unreadCount: 1, messageType: message.type, messageFlow: message.flow } this.triggerCallback('onConversationListUpdated', { reason: 'NEW_MESSAGE_RECEIVED', conversation: conversationUpdate, message: message }) } catch (error) { console.error('更新会话列表失败:', error) } } // 设置回调函数 setCallback(event, callback) { this.callbacks[event] = callback } // 触发回调 triggerCallback(event, data) { if (this.callbacks[event]) { this.callbacks[event](data) } } // 清理资源(优化版:更完整的清理) destroy() { console.log('=== 开始清理IM资源 ===') // 清理所有定时器(使用统一方法) this.clearAllTimers() // 清理消息缓存(已弃用) console.log('缓存功能已移除') // 清理TIM实例 if (this.tim) { try { // 移除事件监听器 this.removeEventListeners() // 退出登录(同步处理,不等待结果) if (typeof this.tim.logout === 'function') { this.tim.logout() .then(() => console.log('✓ TIM登出成功')) .catch(error => console.warn('TIM登出失败:', error)) } } catch (error) { console.error('清理TIM时出错:', error) } } // 重置所有状态(使用统一方法) this.resetAllStates() // 清理其他状态 this.messageList = [] this.conversation = null this.currentUserID = '' this.currentUserSig = '' this.currentConversationID = null this.tim = null console.log('=== IM资源清理完成 ===') } } // 创建全局IM管理器实例 const globalTimChatManager = new TimChatManager() // 全局IM初始化函数 - 支持强制重新初始化 let initPromise = null; const initGlobalTIM = async (userID, forceReinit = false) => { console.log('开始初始化全局IM,userID:', userID, 'forceReinit:', forceReinit) if (globalTimChatManager.isInitializing) { console.log('全局IM正在初始化中,跳过重复初始化') return true } if (forceReinit && globalTimChatManager.tim) { console.log('强制重新初始化,清理现有IM状态') globalTimChatManager.isLoggedIn = false globalTimChatManager.lastLoginTime = 0 globalTimChatManager.reconnectAttempts = 0 if (globalTimChatManager.tim.isLoggedIn) { await globalTimChatManager.tim.logout() console.log('强制重新初始化:TIM登出成功') } await globalTimChatManager.initTIM(userID) console.log('强制重新初始化完成') return true } if (!forceReinit && globalTimChatManager.tim && globalTimChatManager.isLoggedIn) { console.log('全局IM已经初始化并登录,跳过重复初始化') return true } await globalTimChatManager.initTIM(userID) console.log('全局IM初始化成功') return true } const getInitIMPromise = async (userID, forceReinit) => { if (initPromise) return initPromise; initPromise = initGlobalTIM(userID, forceReinit); return initPromise; } const clearInitIMPromise = () => { initPromise = null; } // 获取群聊列表(全局函数) const getGroupList = async () => { return await globalTimChatManager.getGroupList() } // 进入群聊 const enterChatGroupRoom = async (chatGroup, navigateType = 'navigateTo', viewType) => { const groupListResult = await globalTimChatManager.getGroupList(); const groupList = groupListResult && Array.isArray(groupListResult.groupList) ? groupListResult.groupList : []; let group = groupList.find(g => g.groupID === chatGroup.groupID); const navigateFn = ['navigateTo', 'redirectTo', 'reLaunch'].includes(navigateType) ? navigateType : 'navigateTo' uni[navigateFn]({ url: `/pages-im/IM/index?conversationID=GROUP${chatGroup.groupID}&groupID=${chatGroup.groupID}&conversationType=GROUP&viewType=${viewType}`, }) } // 检查全局IM状态 const checkGlobalIMStatus = () => { return globalTimChatManager.isLoggedIn } // 清理指定会话的缓存 const clearConversationCache = (conversationID) => { return globalTimChatManager.clearConversationCache(conversationID) } // 清理所有消息缓存 const clearAllMessageCache = () => { return globalTimChatManager.clearAllCache() } // 确保全局IM连接 const ensureGlobalIMConnection = async () => { return await globalTimChatManager.ensureIMConnection() } // 获取全局IM登录状态 const getGlobalIMLoginStatus = () => { return globalTimChatManager.isLoggedIn } // 设置全局IM回调 const setGlobalIMCallback = (event, callback) => { globalTimChatManager.setCallback(event, callback) } // 导出 export { TimChatManager, globalTimChatManager, initGlobalTIM, getInitIMPromise, clearInitIMPromise, enterChatGroupRoom, checkGlobalIMStatus, ensureGlobalIMConnection, getGlobalIMLoginStatus, setGlobalIMCallback, getGroupList, clearConversationCache, clearAllMessageCache } export default globalTimChatManager