ykt-wxapp/utils/tim-chat.js
2026-01-26 18:08:01 +08:00

2817 lines
82 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 引入腾讯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('开始登录腾讯IMuserID:', 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
// 检查是否为系统消息
const isSystemMsg = this.isSystemMessage(convertedMessage)
console.log('收到新消息:', {
messageID: convertedMessage.ID,
messageConversationID: messageConversationID,
currentConversationID: this.currentConversationID,
messageType: convertedMessage.type,
from: convertedMessage.from,
isSystemMessage: isSystemMsg
})
// 判断是否为当前会话的消息
// 系统消息只要会话ID匹配就显示不要求必须有currentConversationID
// 普通消息必须有currentConversationID且匹配才显示
const isCurrentConversation = isSystemMsg
? messageConversationID === this.currentConversationID
: (this.currentConversationID && messageConversationID === this.currentConversationID)
console.log('消息会话匹配检查:', {
isCurrentConversation,
isSystemMessage: isSystemMsg,
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(`<EFBFBD> 心跳恢复正常(之前失败${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') {
// 解析自定义消息
try {
const customData = JSON.parse(lastMessage.payload.data)
const messageType = customData.messageType
// 根据消息类型返回不同的预览文本
switch (messageType) {
case 'system_message':
lastMessageText = '[系统消息]'
break
case 'symptom':
lastMessageText = '[病情描述]'
break
case 'prescription':
lastMessageText = '[处方单]'
break
case 'refill':
lastMessageText = '[续方申请]'
break
case 'survey':
lastMessageText = '[问卷调查]'
break
case 'article':
lastMessageText = '[文章]'
break
case "consult_pending":
lastMessageText = '患者向团队发起咨询请在1小时内接诊超时将自动关闭会话'
break
case "consult_rejected":
lastMessageText = '患者向团队发起咨询,由于有紧急事务要处理暂时无法接受咨询.本次会话丿关闭'
break
case "consult_timeout":
lastMessageText = '患者向团队发起咨询,团队成员均未接受咨询,本次会话已自动关闭'
break
case "consult_accepted":
lastMessageText = '已接诊,会话已开始'
break
case "consult_ended":
lastMessageText = '已结束当前会话'
break
default:
lastMessageText = '[自定义消息]'
}
} catch (error) {
console.error('解析自定义消息失败:', error)
lastMessageText = '[自定义消息]'
}
} 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('无法获取群聊IDconversationID:', 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) {
if (!this.tim) {
this.triggerCallback('onError', 'IM未初始化')
return
}
if (!this.conversation) {
this.triggerCallback('onError', '群聊会话不存在')
return { success: false, error: '群聊会话不存在' }
}
let groupID = null
if (this.conversation.groupProfile && this.conversation.groupProfile.groupID) {
groupID = this.conversation.groupProfile.groupID
} else if (this.conversation.conversationID) {
groupID = this.conversation.conversationID.replace('GROUP', '')
}
if (!groupID) {
this.triggerCallback('onError', '无法获取群聊ID')
return { success: false, error: '无法获取群聊ID' }
}
// 确保使用当前会话的conversationID
const conversationID = this.conversation.conversationID || this.currentConversationID
// 获取图片尺寸信息
const imageInfo = await this.getImageInfo(imageFile);
const localMessage = {
ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
flow: 'out',
type: 'TIMImageElem',
payload: {
imageInfoArray: [{
url: this.getImageUrl(imageFile),
width: imageInfo.width,
height: imageInfo.height
}]
},
lastTime: Date.now(),
status: 'sending',
avatar: '',
conversationID: conversationID,
from: this.currentUserID
}
console.log('创建本地图片消息:', localMessage)
// 缓存功能已移除
// 触发消息接收回调让UI立即显示
this.triggerCallback('onMessageReceived', localMessage)
const message = this.tim.createImageMessage({
to: groupID,
conversationType: TIM.TYPES.CONV_GROUP,
payload: { file: imageFile }
})
try {
await this.tim.sendMessage(message)
localMessage.status = 'success'
return { success: true, message: localMessage }
} catch (error) {
console.error('图片消息发送失败:', error)
localMessage.status = 'failed'
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('无法获取群聊IDconversationID:', 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('无法获取群聊IDconversationID:', 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 }
}
}
// 工具方法
// 判断是否为系统消息
isSystemMessage(message) {
if (message.type !== 'TIMCustomElem') {
return false
}
// 检查 payload.data 是否包含系统消息标记
try {
if (message.payload && message.payload.data) {
const data = typeof message.payload.data === 'string'
? JSON.parse(message.payload.data)
: message.payload.data
// 检查是否为系统消息类型
if (data.type === 'system_message') {
return true
}
}
// 检查 description 是否为系统消息标记
if (message.payload && message.payload.description === '系统消息标记') {
return true
}
// 兼容旧的系统消息格式
if (message.payload && message.payload.description === 'SYSTEM_NOTIFICATION') {
return true
}
} catch (error) {
console.error('判断系统消息失败:', error)
}
return false
}
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) {
// 处理 tempFiles 数组格式
if (imageFile?.tempFiles?.length > 0) {
return imageFile.tempFiles[0].tempFilePath
}
// 处理单个文件对象
if (imageFile?.tempFilePath) {
return imageFile.tempFilePath
}
// 处理字符串路径
if (typeof imageFile === 'string') {
return imageFile
}
console.warn('无法获取图片URL使用默认图片:', imageFile);
return '/static/home/photo.png'
}
// 获取图片尺寸信息
getImageInfo(imageFile) {
return new Promise((resolve) => {
let imagePath = '';
// 获取图片路径 - 处理多种格式
if (imageFile?.tempFilePaths?.length > 0) {
// wx.chooseImage 返回的对象
imagePath = imageFile.tempFilePaths[0];
} else if (imageFile?.tempFiles?.length > 0) {
// 从 tempFiles 中提取路径
const tempFile = imageFile.tempFiles[0];
imagePath = tempFile.path || tempFile.tempFilePath;
} else if (imageFile?.tempFilePath) {
imagePath = imageFile.tempFilePath;
} else if (typeof imageFile === 'string') {
imagePath = imageFile;
} else {
console.warn('无法获取图片路径,使用默认尺寸:', imageFile);
// 默认尺寸
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('开始初始化全局IMuserID:', 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