2026-01-20 13:21:50 +08:00
|
|
|
|
// 引入腾讯IM SDK
|
|
|
|
|
|
import TIM from 'tim-wx-sdk'
|
|
|
|
|
|
import TIMUploadPlugin from 'tim-upload-plugin'
|
2026-01-22 15:13:26 +08:00
|
|
|
|
import api from './api.js'
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
|
|
|
|
|
const env = __VITE_ENV__;
|
|
|
|
|
|
|
|
|
|
|
|
// 腾讯IM配置 - SDKAppID 必须是 number 类型,使用 Number() 转换
|
|
|
|
|
|
const TIM_CONFIG = {
|
2026-01-22 15:13:26 +08:00
|
|
|
|
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
|
|
|
|
|
|
})
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 用户信息
|
|
|
|
|
|
this.currentUserID = ''
|
|
|
|
|
|
this.currentUserSig = ''
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 消息管理
|
|
|
|
|
|
this.messageList = []
|
|
|
|
|
|
this.currentConversationID = null
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 分页加载相关状态
|
|
|
|
|
|
this.nextReqMessageID = ""
|
|
|
|
|
|
this.isCompleted = false
|
|
|
|
|
|
this.isLoadingMore = false
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 状态标志
|
|
|
|
|
|
this.isLoading = false
|
|
|
|
|
|
this.isLoggedIn = false
|
|
|
|
|
|
this.isInitializing = false
|
|
|
|
|
|
this.isLoggingIn = false
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 定时器
|
|
|
|
|
|
this.loginCheckInterval = null
|
|
|
|
|
|
this.heartbeatInterval = null
|
|
|
|
|
|
this.networkReconnectTimer = null
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 重连管理
|
|
|
|
|
|
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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 心跳管理
|
|
|
|
|
|
this.heartbeatFailCount = 0
|
|
|
|
|
|
|
|
|
|
|
|
// 回调函数
|
|
|
|
|
|
this.callbacks = {
|
|
|
|
|
|
onMessageReceived: null,
|
|
|
|
|
|
onMessageSent: null,
|
|
|
|
|
|
onSDKReady: null,
|
|
|
|
|
|
onSDKNotReady: null,
|
|
|
|
|
|
onError: null,
|
|
|
|
|
|
onLoginStatusChanged: null,
|
|
|
|
|
|
onConversationListUpdated: null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 15:13:26 +08:00
|
|
|
|
// 绑定事件处理函数
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============== 资源管理方法 ==============
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清理所有定时器
|
|
|
|
|
|
clearAllTimers() {
|
|
|
|
|
|
if (this.loginCheckInterval) {
|
|
|
|
|
|
clearTimeout(this.loginCheckInterval)
|
|
|
|
|
|
clearInterval(this.loginCheckInterval)
|
|
|
|
|
|
this.loginCheckInterval = null
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
if (this.heartbeatInterval) {
|
|
|
|
|
|
clearInterval(this.heartbeatInterval)
|
|
|
|
|
|
this.heartbeatInterval = null
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
if (this.networkReconnectTimer) {
|
|
|
|
|
|
clearTimeout(this.networkReconnectTimer)
|
|
|
|
|
|
this.networkReconnectTimer = null
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log('所有定时器已清理')
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清理缓存(已弃用缓存功能)
|
|
|
|
|
|
cleanupCache() {
|
|
|
|
|
|
// 缓存功能已移除
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 重置所有状态
|
|
|
|
|
|
resetAllStates() {
|
|
|
|
|
|
this.isLoggedIn = false
|
|
|
|
|
|
this.isLoggingIn = false
|
|
|
|
|
|
this.isInitializing = false
|
|
|
|
|
|
this.reconnectAttempts = 0
|
|
|
|
|
|
this.lastLoginTime = 0
|
|
|
|
|
|
this.heartbeatFailCount = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============== 初始化方法 ==============
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 初始化腾讯IM
|
|
|
|
|
|
async initTIM(userID = null) {
|
|
|
|
|
|
if (this.isInitializing) {
|
|
|
|
|
|
console.log('IM正在初始化中,跳过重复初始化')
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.isInitializing = true
|
|
|
|
|
|
console.log('=== 开始初始化IM ===')
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 重置重连次数,允许重新登录
|
|
|
|
|
|
this.reconnectAttempts = 0
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 如果存在旧的TIM实例,先完整清理
|
|
|
|
|
|
if (this.tim) {
|
|
|
|
|
|
console.log('检测到旧的TIM实例,开始清理...')
|
|
|
|
|
|
await this.cleanupOldInstance()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!TIM) {
|
|
|
|
|
|
throw new Error('TIM SDK 未正确导入')
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
// 验证 SDKAppID
|
|
|
|
|
|
if (!TIM_CONFIG.SDKAppID || isNaN(TIM_CONFIG.SDKAppID)) {
|
|
|
|
|
|
throw new Error(`TIM SDK配置错误: SDKAppID无效 (${TIM_CONFIG.SDKAppID}),请检查环境变量 MP_TIM_SDK_APP_ID`)
|
|
|
|
|
|
}
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
2026-01-22 15:13:26 +08:00
|
|
|
|
console.log('创建TIM实例,SDKAppID:', TIM_CONFIG.SDKAppID)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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}`)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 初始化失败时清理资源
|
|
|
|
|
|
await this.cleanupOldInstance()
|
|
|
|
|
|
return false
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.isInitializing = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清理旧的TIM实例
|
|
|
|
|
|
async cleanupOldInstance() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 清理所有定时器
|
|
|
|
|
|
this.clearAllTimers()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 移除事件监听器
|
|
|
|
|
|
this.removeEventListeners()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 登出
|
|
|
|
|
|
if (this.tim && this.tim.isLoggedIn) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this.tim.logout()
|
|
|
|
|
|
console.log('已登出旧实例')
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.warn('登出旧实例失败:', err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清理实例
|
|
|
|
|
|
this.tim = null
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 重置状态
|
|
|
|
|
|
this.resetAllStates()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log('旧实例清理完成')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('清理旧实例时出错:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 等待TIM实例准备就绪
|
|
|
|
|
|
waitForTIMInstanceReady() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const startTime = Date.now()
|
|
|
|
|
|
const timeout = 10000 // 10秒超时
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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 {
|
2026-01-22 15:13:26 +08:00
|
|
|
|
console.log(`等待SDK Ready... ${Math.floor(elapsed / 1000)}/${Math.floor(timeout / 1000)}秒`)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 15:13:26 +08:00
|
|
|
|
// 逐个注册事件监听器,如果某个注册失败则继续注册其他的
|
|
|
|
|
|
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 }
|
|
|
|
|
|
]
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
|
|
|
|
|
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 {
|
2026-01-22 15:13:26 +08:00
|
|
|
|
const response = await api('getUserSig', { userId: userID })
|
|
|
|
|
|
if (response?.success && response?.data) {
|
|
|
|
|
|
return response.data
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
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(() => {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.log('腾讯IM登录请求成功,等待SDK_READY事件...')
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.isLoggingIn = false
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 不在这里设置 isLoggedIn = true,等待 onSDKReady 事件
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.reconnectAttempts = 0
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 触发登录状态变化回调(登录中状态)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.triggerCallback('onLoginStatusChanged', {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
isLoggedIn: false,
|
2026-01-20 13:21:50 +08:00
|
|
|
|
userID: this.currentUserID,
|
2026-01-22 16:35:05 +08:00
|
|
|
|
reason: 'LOGIN_REQUESTED',
|
|
|
|
|
|
message: '登录请求成功,等待SDK就绪...'
|
2026-01-20 13:21:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
resolve()
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
|
console.error('腾讯IM登录失败:', error)
|
|
|
|
|
|
this.isLoggingIn = false
|
|
|
|
|
|
this.triggerCallback('onLoginStatusChanged', {
|
|
|
|
|
|
isLoggedIn: false,
|
|
|
|
|
|
error,
|
|
|
|
|
|
reason: 'LOGIN_FAILED'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============== 连接监控方法 ==============
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 启动登录状态检测(优化版:使用配置常量)
|
|
|
|
|
|
startLoginStatusCheck() {
|
|
|
|
|
|
this.stopLoginStatusCheck()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 根据连接稳定性动态调整检查间隔
|
|
|
|
|
|
const getCheckInterval = () => {
|
|
|
|
|
|
if (this.reconnectAttempts > 0) {
|
|
|
|
|
|
return IM_CONNECTION_CONFIG.LOGIN_CHECK_INTERVAL_UNSTABLE
|
|
|
|
|
|
}
|
|
|
|
|
|
return IM_CONNECTION_CONFIG.LOGIN_CHECK_INTERVAL_STABLE
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
const checkStatus = () => {
|
|
|
|
|
|
// 如果已达到最大重连次数(被踢下线),停止检查
|
|
|
|
|
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
|
|
|
|
console.log('⚠️ 已被踢下线或达到最大重连次数,停止登录状态检查')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 如果正在登录中,跳过本次检查
|
|
|
|
|
|
if (this.isLoggingIn) {
|
|
|
|
|
|
this.loginCheckInterval = setTimeout(checkStatus, getCheckInterval())
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
const isLoggedIn = this.checkLoginStatus()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 如果未登录且有用户ID,尝试重连
|
|
|
|
|
|
if (!isLoggedIn && this.currentUserID && !this.isLoggingIn) {
|
|
|
|
|
|
console.log('📡 登录状态检测:发现未登录,尝试重连')
|
|
|
|
|
|
this.attemptReconnect()
|
|
|
|
|
|
} else if (isLoggedIn) {
|
|
|
|
|
|
// 登录正常,重置重连计数
|
|
|
|
|
|
if (this.reconnectAttempts > 0) {
|
|
|
|
|
|
console.log('✓ 登录状态检测:连接已恢复正常')
|
|
|
|
|
|
this.reconnectAttempts = 0
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 定期清理缓存
|
|
|
|
|
|
this.cleanupCache()
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 重新调度下一次检查(再次检查,防止在检查过程中被踢下线)
|
|
|
|
|
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
|
|
|
|
this.loginCheckInterval = setTimeout(checkStatus, getCheckInterval())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 首次检查延迟
|
|
|
|
|
|
const firstDelay = IM_CONNECTION_CONFIG.LOGIN_CHECK_FIRST_DELAY
|
|
|
|
|
|
this.loginCheckInterval = setTimeout(checkStatus, firstDelay)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
console.log(`🔍 登录状态检测已启动,首次检查将在${firstDelay / 1000}秒后进行`)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 停止登录状态检测
|
|
|
|
|
|
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 // 默认保持登录状态
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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)
|
|
|
|
|
|
// 出错时保持当前状态
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 只有 SDK 明确返回未登录时才更新状态
|
|
|
|
|
|
if (!sdkLoginStatus) {
|
|
|
|
|
|
this.isLoggedIn = false
|
|
|
|
|
|
this.triggerCallback('onLoginStatusChanged', {
|
|
|
|
|
|
isLoggedIn: false,
|
|
|
|
|
|
userID: this.currentUserID,
|
|
|
|
|
|
reason: 'SDK_STATUS_CHECK'
|
|
|
|
|
|
})
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 保持登录状态
|
|
|
|
|
|
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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 只有非首次重连才需要检查冷却时间
|
|
|
|
|
|
if (this.reconnectAttempts > 0 && timeSinceLastLogin < reconnectDelay) {
|
|
|
|
|
|
const remainingTime = reconnectDelay - timeSinceLastLogin
|
|
|
|
|
|
console.log(`重连冷却中,剩余时间:${Math.ceil(remainingTime / 1000)}秒`)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 安排下次重连
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (!this.isLoggedIn && !this.isLoggingIn && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
|
|
|
|
this.attemptReconnect()
|
|
|
|
|
|
}
|
|
|
|
|
|
}, remainingTime)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 如果还有重连机会,自动安排下一次重连
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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
|
|
|
|
|
|
})
|
2026-01-22 15:13:26 +08:00
|
|
|
|
// 判断是否为当前会话的消息(必须有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)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 处理已读状态
|
|
|
|
|
|
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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 更新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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 更新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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log('🌐 网络状态变化:', netState)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清理之前的网络重连定时器
|
|
|
|
|
|
if (this.networkReconnectTimer) {
|
|
|
|
|
|
clearTimeout(this.networkReconnectTimer)
|
|
|
|
|
|
this.networkReconnectTimer = null
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
if (netState === TIM.TYPES.NET_STATE_CONNECTED) {
|
|
|
|
|
|
console.log('✓ 网络已连接,延迟检查IM状态以确保稳定')
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 网络恢复后延迟再检查,避免网络还不稳定时立即重连
|
|
|
|
|
|
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)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 重置重连次数(网络恢复后给更多机会)
|
|
|
|
|
|
if (this.reconnectAttempts > 0) {
|
|
|
|
|
|
console.log(`重置重连次数(之前: ${this.reconnectAttempts})`)
|
|
|
|
|
|
this.reconnectAttempts = 0
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
} else if (netState === TIM.TYPES.NET_STATE_CONNECTING) {
|
|
|
|
|
|
console.log('🔗 网络连接中,等待稳定...')
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
} else if (netState === TIM.TYPES.NET_STATE_DISCONNECTED) {
|
|
|
|
|
|
console.log('⚠️ 网络断开(暂不标记为未登录,等待心跳检测判断)')
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 网络断开时不立即设置 isLoggedIn = false
|
|
|
|
|
|
// 因为可能只是短暂的网络波动,让心跳检测来判断是否真的断线
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.triggerCallback('onLoginStatusChanged', {
|
|
|
|
|
|
isLoggedIn: this.isLoggedIn,
|
|
|
|
|
|
reason: 'NETWORK_DISCONNECTED',
|
|
|
|
|
|
message: '网络波动,将自动重连'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 被踢下线处理
|
|
|
|
|
|
onKickedOut(event) {
|
|
|
|
|
|
console.log('⚠️ 账号被踢下线:', event.data)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 更新登录状态
|
|
|
|
|
|
this.isLoggedIn = false
|
|
|
|
|
|
this.isLoggingIn = false
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 停止所有定时器(防止自动重连)
|
|
|
|
|
|
this.clearAllTimers()
|
|
|
|
|
|
this.stopLoginStatusCheck()
|
|
|
|
|
|
this.stopHeartbeat()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清除重连相关状态,阻止重连
|
|
|
|
|
|
this.reconnectAttempts = this.maxReconnectAttempts // 设置为最大值,阻止重连
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 触发登录状态变化回调
|
|
|
|
|
|
this.triggerCallback('onLoginStatusChanged', {
|
|
|
|
|
|
isLoggedIn: false,
|
|
|
|
|
|
reason: 'KICKED_OUT',
|
|
|
|
|
|
message: '您的账号在其他设备登录'
|
|
|
|
|
|
})
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 显示提示弹框
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提示',
|
|
|
|
|
|
content: '您的账号在其他设备登录,您已被踢下线',
|
|
|
|
|
|
showCancel: false,
|
|
|
|
|
|
confirmText: '确定',
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
// 清理本地缓存并跳转到登录页
|
|
|
|
|
|
uni.removeStorageSync('token')
|
|
|
|
|
|
uni.removeStorageSync('refreshToken')
|
|
|
|
|
|
uni.removeStorageSync('account')
|
|
|
|
|
|
uni.removeStorageSync('openid')
|
|
|
|
|
|
uni.reLaunch({
|
2026-01-22 15:13:26 +08:00
|
|
|
|
url: '/pages-center/login/login'
|
2026-01-20 13:21:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 手动检测并重连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()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 心跳失败计数器
|
|
|
|
|
|
this.heartbeatFailCount = 0
|
|
|
|
|
|
const MAX_HEARTBEAT_FAIL = IM_CONNECTION_CONFIG.HEARTBEAT_MAX_FAIL
|
|
|
|
|
|
const INTERVAL = IM_CONNECTION_CONFIG.HEARTBEAT_INTERVAL
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 定时心跳检测
|
|
|
|
|
|
this.heartbeatInterval = setInterval(() => {
|
|
|
|
|
|
// 如果已达到最大重连次数(被踢下线),停止心跳检测
|
|
|
|
|
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
|
|
|
|
console.log('⚠️ 已被踢下线或达到最大重连次数,停止心跳检测')
|
|
|
|
|
|
this.stopHeartbeat()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 只在已登录状态下进行心跳检测
|
|
|
|
|
|
if (!this.tim || !this.isLoggedIn) {
|
|
|
|
|
|
console.log('⏸ 心跳检测:未登录,跳过检测')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 确保方法存在
|
|
|
|
|
|
if (typeof this.tim.getConversationList !== 'function') {
|
|
|
|
|
|
console.log('⏸ 心跳检测:SDK方法不可用,跳过检测')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.tim.getConversationList()
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
if (this.heartbeatFailCount > 0) {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.log(`<EFBFBD> 心跳恢复正常(之前失败${this.heartbeatFailCount}次)`)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
this.heartbeatFailCount = 0 // 重置失败计数
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((error) => {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 如果是SDK未就绪错误,不计入失败次数(这是临时状态)
|
|
|
|
|
|
if (error && error.message && error.message.includes('sdk not ready')) {
|
|
|
|
|
|
console.log('⏸ 心跳检测:SDK未就绪,跳过本次检测')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.heartbeatFailCount++
|
|
|
|
|
|
console.error(`💔 心跳失败 (${this.heartbeatFailCount}/${MAX_HEARTBEAT_FAIL}):`, error.message)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 只有连续失败多次才认为真的断线
|
|
|
|
|
|
if (this.heartbeatFailCount >= MAX_HEARTBEAT_FAIL) {
|
|
|
|
|
|
console.log('❌ 心跳连续失败,标记为未登录并尝试重连')
|
|
|
|
|
|
this.isLoggedIn = false
|
|
|
|
|
|
this.heartbeatFailCount = 0
|
|
|
|
|
|
// 只有未被踢下线才尝试重连
|
|
|
|
|
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
|
|
|
|
this.attemptReconnect()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}, INTERVAL)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
|
|
|
|
|
console.log(`💓 心跳检测已启动(间隔${INTERVAL / 1000}秒,最多失败${MAX_HEARTBEAT_FAIL}次)`)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 停止心跳检测
|
|
|
|
|
|
stopHeartbeat() {
|
|
|
|
|
|
if (this.heartbeatInterval) {
|
|
|
|
|
|
clearInterval(this.heartbeatInterval)
|
|
|
|
|
|
this.heartbeatInterval = null
|
|
|
|
|
|
console.log('IM心跳检测已停止')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取会话列表
|
|
|
|
|
|
getConversationList() {
|
|
|
|
|
|
if (!this.tim) {
|
|
|
|
|
|
console.error('TIM实例不存在,无法获取会话列表')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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初始化...')
|
2026-01-22 15:13:26 +08:00
|
|
|
|
let waitTime = 0
|
|
|
|
|
|
const maxWaitTime = 30000 // 最多等待30秒
|
|
|
|
|
|
const checkInterval = 1000 // 每秒检查一次
|
2026-01-22 16:35:05 +08:00
|
|
|
|
let timeoutHandle = null
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
const checkSDKReady = () => {
|
|
|
|
|
|
if (this.isLoggedIn) {
|
|
|
|
|
|
console.log('SDK已ready,开始获取群聊列表')
|
2026-01-22 16:35:05 +08:00
|
|
|
|
if (timeoutHandle) clearTimeout(timeoutHandle)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.getGroupListInternal().then(resolve).catch(reject)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
} else if (waitTime >= maxWaitTime) {
|
|
|
|
|
|
console.error('等待SDK就绪超时')
|
2026-01-22 16:35:05 +08:00
|
|
|
|
if (timeoutHandle) clearTimeout(timeoutHandle)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
reject(new Error('SDK初始化超时,请检查网络连接'))
|
2026-01-20 13:21:50 +08:00
|
|
|
|
} else {
|
2026-01-22 15:13:26 +08:00
|
|
|
|
waitTime += checkInterval
|
|
|
|
|
|
console.log(`等待SDK就绪... (${Math.floor(waitTime / 1000)}/${Math.floor(maxWaitTime / 1000)}秒)`)
|
2026-01-22 16:35:05 +08:00
|
|
|
|
timeoutHandle = setTimeout(checkSDKReady, checkInterval)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
checkSDKReady()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.getGroupListInternal().then(resolve).catch(reject)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 内部获取群聊列表方法
|
|
|
|
|
|
getGroupListInternal() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
console.log('开始获取群聊列表')
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 直接调用,SDK就绪检查已在getGroupList()中完成
|
|
|
|
|
|
this.tim.getConversationList()
|
|
|
|
|
|
.then(async (conversationResponse) => {
|
|
|
|
|
|
console.log('获取会话列表成功')
|
|
|
|
|
|
|
|
|
|
|
|
const groupConversations = conversationResponse.data.conversationList.filter(conversation => {
|
|
|
|
|
|
return conversation.conversationID && conversation.conversationID.startsWith('GROUP')
|
|
|
|
|
|
})
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.log('群聊会话列表数量:', groupConversations.length)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 先获取一次群组列表,避免在循环中重复调用
|
|
|
|
|
|
let allGroups = []
|
|
|
|
|
|
try {
|
|
|
|
|
|
const groupListResponse = await this.tim.getGroupList()
|
|
|
|
|
|
allGroups = groupListResponse.data.groupList || []
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取群组列表失败:', error)
|
|
|
|
|
|
}
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
const groupsWithInfo = await Promise.all(
|
|
|
|
|
|
groupConversations.map(async (conversation) => {
|
2026-01-20 13:21:50 +08:00
|
|
|
|
try {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
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
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
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
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
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
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 16:35:05 +08:00
|
|
|
|
})
|
|
|
|
|
|
)
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.log('处理后的群聊列表数量:', groupsWithInfo.length)
|
|
|
|
|
|
resolve({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
groupList: groupsWithInfo,
|
|
|
|
|
|
totalCount: groupsWithInfo.length,
|
|
|
|
|
|
data: conversationResponse.data
|
2026-01-20 13:21:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
})
|
2026-01-22 16:35:05 +08:00
|
|
|
|
.catch((imError) => {
|
|
|
|
|
|
console.error('获取会话列表失败:', imError)
|
|
|
|
|
|
reject({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: imError
|
|
|
|
|
|
})
|
2026-01-20 13:21:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建问诊群聊
|
|
|
|
|
|
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 {
|
2026-01-22 15:13:26 +08:00
|
|
|
|
await api('sendSystemMessage', { groupID, status: 'pending' })
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 更新当前会话ID
|
|
|
|
|
|
this.currentConversationID = conversationID
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清空当前消息列表
|
|
|
|
|
|
this.messageList = []
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 重置分页状态
|
|
|
|
|
|
this.nextReqMessageID = ""
|
|
|
|
|
|
this.isCompleted = false
|
|
|
|
|
|
this.isLoadingMore = false
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log(" 会话ID已更新,消息列表已清空,分页状态已重置")
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 进入群聊会话,默认加载20条消息
|
|
|
|
|
|
this.enterGroupConversation(conversationID, 20)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 进入群聊会话
|
|
|
|
|
|
async enterGroupConversation(groupID, count = 20) {
|
|
|
|
|
|
console.log("【enterGroupConversation】进入群聊会话, groupID:", groupID, "count:", count)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 调用本地接口获取聊天记录
|
2026-01-22 15:13:26 +08:00
|
|
|
|
const response = await api('getChatRecordsByGroupId', { groupID, count, skip })
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log(` 成功获取 ${dbMessages.length} 条聊天记录`)
|
|
|
|
|
|
console.log(` hasMore: ${hasMore}, total: ${total}`)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 将数据库消息转换为IM消息格式
|
|
|
|
|
|
const convertedMessages = dbMessages
|
|
|
|
|
|
.map(dbMsg => this.convertDBMessageToIMFormat(dbMsg))
|
|
|
|
|
|
.filter(msg => msg !== null && this.filterMessage(msg))
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log(` 转换后 ${convertedMessages.length} 条消息`)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 按时间排序(从早到晚)
|
|
|
|
|
|
convertedMessages.sort((a, b) => a.lastTime - b.lastTime)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 根据是否为上拉加载,决定如何更新消息列表
|
|
|
|
|
|
if (isPullUp) {
|
|
|
|
|
|
// 上拉加载更多:将新消息插入到列表前面(历史消息)
|
|
|
|
|
|
this.messageList.unshift(...convertedMessages)
|
|
|
|
|
|
// 再次排序确保顺序正确
|
|
|
|
|
|
this.messageList.sort((a, b) => a.lastTime - b.lastTime)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 首次加载:直接替换消息列表
|
|
|
|
|
|
this.messageList = convertedMessages
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 设置分页状态 - 使用后端返回的 hasMore 字段
|
|
|
|
|
|
this.isCompleted = !hasMore
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log(` 分页状态: isCompleted=${this.isCompleted}, hasMore=${hasMore}`)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 触发回调
|
|
|
|
|
|
this.triggerCallback("onMessageListLoaded", {
|
|
|
|
|
|
messages: this.messageList,
|
|
|
|
|
|
isPullUp: isPullUp,
|
|
|
|
|
|
hasMore: hasMore,
|
|
|
|
|
|
isCompleted: this.isCompleted,
|
|
|
|
|
|
total: total,
|
|
|
|
|
|
})
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 将数据库消息转换为IM消息格式
|
|
|
|
|
|
convertDBMessageToIMFormat(dbMsg) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!dbMsg || !dbMsg.MsgBody || !Array.isArray(dbMsg.MsgBody) || dbMsg.MsgBody.length === 0) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 获取第一个消息体(通常一个消息只有一个消息体)
|
|
|
|
|
|
const msgBody = dbMsg.MsgBody[0]
|
|
|
|
|
|
const msgType = msgBody.MsgType
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 确定消息的流向(in/out)
|
|
|
|
|
|
// 根据 From_Account 判断是否为当前用户发送的消息
|
|
|
|
|
|
const flow = dbMsg.From_Account === this.currentUserID ? 'out' : 'in'
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 计算时间戳(毫秒)
|
|
|
|
|
|
let lastTime = Date.now()
|
|
|
|
|
|
if (dbMsg.MsgTime) {
|
|
|
|
|
|
// MsgTime 是秒级时间戳
|
|
|
|
|
|
lastTime = dbMsg.MsgTime * 1000
|
|
|
|
|
|
} else if (dbMsg.createdAt) {
|
|
|
|
|
|
lastTime = new Date(dbMsg.createdAt).getTime()
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 构建基础消息对象
|
|
|
|
|
|
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',
|
2026-01-22 16:35:05 +08:00
|
|
|
|
avatar: flow === 'in' ? '/static/home/avatar.svg' : '/static/center/user-avatar.png',
|
2026-01-20 13:21:50 +08:00
|
|
|
|
conversationID: this.currentConversationID,
|
|
|
|
|
|
MsgSeq: dbMsg.MsgSeq, // 保留 MsgSeq 用于分页
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
return message
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("转换数据库消息格式失败:", error, dbMsg)
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 将数据库消息体转换为IM payload格式
|
|
|
|
|
|
convertDBPayloadToIMPayload(msgType, msgContent) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!msgContent) {
|
|
|
|
|
|
return {}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
switch (msgType) {
|
|
|
|
|
|
case 'TIMTextElem':
|
|
|
|
|
|
return {
|
|
|
|
|
|
text: msgContent.Text || ''
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
case 'TIMImageElem':
|
|
|
|
|
|
return {
|
|
|
|
|
|
imageInfoArray: msgContent.ImageInfoArray || []
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
case 'TIMSoundElem':
|
|
|
|
|
|
return {
|
|
|
|
|
|
url: msgContent.Url || '',
|
|
|
|
|
|
second: msgContent.Second || 0,
|
|
|
|
|
|
downloadFlag: msgContent.Download_Flag || 2
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
case 'TIMCustomElem':
|
|
|
|
|
|
return {
|
|
|
|
|
|
data: msgContent.Data || '',
|
|
|
|
|
|
description: msgContent.Desc || '',
|
|
|
|
|
|
extension: msgContent.Ext || ''
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
case 'TIMFileElem':
|
|
|
|
|
|
return {
|
|
|
|
|
|
url: msgContent.Url || '',
|
|
|
|
|
|
fileSize: msgContent.FileSize || 0,
|
|
|
|
|
|
fileName: msgContent.FileName || '',
|
|
|
|
|
|
downloadFlag: msgContent.Download_Flag || 2
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 获取群组ID
|
|
|
|
|
|
let groupID = ''
|
|
|
|
|
|
if (this.conversation && this.conversation.groupProfile) {
|
|
|
|
|
|
groupID = this.conversation.groupProfile.groupID
|
|
|
|
|
|
} else if (this.currentConversationID) {
|
|
|
|
|
|
groupID = this.currentConversationID.replace('GROUP', '')
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
if (!groupID) {
|
|
|
|
|
|
console.error(" ❌ 无法获取群组ID")
|
|
|
|
|
|
this.isLoadingMore = false
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: "无法获取群组ID",
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log(" 从本地数据库加载更多,groupID:", groupID, "skip:", skip)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 调用本地API加载更多消息
|
|
|
|
|
|
const result = await this.loadMessagesFromLocalAPI(groupID, 20, skip, true)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.messageList = filteredMessages
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log(`消息刷新完成,过滤后${filteredMessages.length}条消息`)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 触发更新回调
|
|
|
|
|
|
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()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 先添加服务器消息
|
|
|
|
|
|
serverMessages.forEach(msg => {
|
|
|
|
|
|
if (msg.ID) {
|
|
|
|
|
|
messageMap.set(msg.ID, msg)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 再添加缓存消息(覆盖服务器消息,保留本地最新状态)
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 转换为数组并按时间排序
|
|
|
|
|
|
const mergedArray = Array.from(messageMap.values())
|
|
|
|
|
|
mergedArray.sort((a, b) => a.lastTime - b.lastTime)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
console.log(`消息合并完成: 服务器${serverMessages.length}条, 缓存${cachedMessages.length}条, 合并后${mergedArray.length}条`)
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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))
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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未初始化')
|
2026-01-22 16:35:05 +08:00
|
|
|
|
return { success: false, error: 'IM未初始化' }
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 检查登录状态
|
|
|
|
|
|
if (!this.isLoggedIn) {
|
|
|
|
|
|
console.error('IM未登录,无法发送消息');
|
|
|
|
|
|
this.triggerCallback('onError', 'IM未登录,请稍后重试')
|
|
|
|
|
|
return { success: false, error: 'IM未登录' }
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 优先使用 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;
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!groupID) {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.error('无法获取群聊ID,conversationID:', conversationID);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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'
|
2026-01-22 16:35:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果是因为未登录导致的失败,尝试重连
|
|
|
|
|
|
if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) {
|
|
|
|
|
|
console.log('检测到未登录错误,尝试重连...');
|
|
|
|
|
|
this.isLoggedIn = false;
|
|
|
|
|
|
this.ensureIMConnection();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
return { success: false, error }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送图片消息
|
|
|
|
|
|
async sendImageMessage(imageFile) {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.log('sendImageMessage 被调用,参数:', imageFile);
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
if (!this.tim) {
|
|
|
|
|
|
this.triggerCallback('onError', 'IM未初始化')
|
2026-01-22 16:35:05 +08:00
|
|
|
|
return { success: false, error: 'IM未初始化' }
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 检查登录状态
|
|
|
|
|
|
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不存在' }
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 从 conversationID 提取 groupID
|
|
|
|
|
|
let groupID = null;
|
|
|
|
|
|
if (conversationID.startsWith('GROUP')) {
|
|
|
|
|
|
groupID = conversationID.replace('GROUP', '');
|
|
|
|
|
|
} else if (this.conversation?.groupProfile?.groupID) {
|
|
|
|
|
|
groupID = this.conversation.groupProfile.groupID;
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!groupID) {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.error('无法获取群聊ID,conversationID:', conversationID);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
this.triggerCallback('onError', '无法获取群聊ID')
|
|
|
|
|
|
return { success: false, error: '无法获取群聊ID' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.log('发送图片消息,conversationID:', conversationID, 'groupID:', groupID);
|
|
|
|
|
|
|
2026-01-22 17:02:15 +08:00
|
|
|
|
// 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;
|
2026-01-22 16:35:05 +08:00
|
|
|
|
}
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
2026-01-22 17:02:15 +08:00
|
|
|
|
console.log('预览路径:', previewPath);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取图片尺寸信息(用于本地预览)
|
|
|
|
|
|
const imageInfo = await this.getImageInfo(previewPath);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
|
|
|
|
|
const localMessage = {
|
|
|
|
|
|
ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
|
|
|
|
flow: 'out',
|
|
|
|
|
|
type: 'TIMImageElem',
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
imageInfoArray: [{
|
2026-01-22 17:02:15 +08:00
|
|
|
|
url: previewPath,
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
2026-01-22 17:02:15 +08:00
|
|
|
|
console.log('准备创建 TIM 图片消息,groupID:', groupID, 'imageFile:', imageFile);
|
2026-01-22 16:35:05 +08:00
|
|
|
|
|
2026-01-22 17:02:15 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 创建图片消息 - 直接传递 wx.chooseImage 的完整返回对象
|
|
|
|
|
|
const message = this.tim.createImageMessage({
|
|
|
|
|
|
to: groupID,
|
|
|
|
|
|
conversationType: TIM.TYPES.CONV_GROUP,
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
file: imageFile // 传递完整的 wx.chooseImage 返回对象
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
2026-01-22 17:02:15 +08:00
|
|
|
|
console.log('TIM 图片消息已创建:', message);
|
2026-01-22 16:35:05 +08:00
|
|
|
|
|
|
|
|
|
|
console.log('开始发送图片消息...');
|
|
|
|
|
|
const sendResult = await this.tim.sendMessage(message);
|
|
|
|
|
|
console.log('图片消息发送成功:', sendResult);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
localMessage.status = 'success'
|
|
|
|
|
|
return { success: true, message: localMessage }
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('图片消息发送失败:', error)
|
2026-01-22 17:02:15 +08:00
|
|
|
|
console.error('错误详情:', {
|
|
|
|
|
|
message: error.message,
|
|
|
|
|
|
stack: error.stack,
|
|
|
|
|
|
imageFile: imageFile
|
|
|
|
|
|
});
|
2026-01-20 13:21:50 +08:00
|
|
|
|
localMessage.status = 'failed'
|
2026-01-22 16:35:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果是因为未登录导致的失败,尝试重连
|
|
|
|
|
|
if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) {
|
|
|
|
|
|
console.log('检测到未登录错误,尝试重连...');
|
|
|
|
|
|
this.isLoggedIn = false;
|
|
|
|
|
|
this.ensureIMConnection();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
return { success: false, error }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送语音消息
|
|
|
|
|
|
async sendVoiceMessage(voiceFile, duration) {
|
|
|
|
|
|
if (!this.tim) {
|
|
|
|
|
|
this.triggerCallback('onError', 'IM未初始化')
|
2026-01-22 16:35:05 +08:00
|
|
|
|
return { success: false, error: 'IM未初始化' }
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 检查登录状态
|
|
|
|
|
|
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不存在' }
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 从 conversationID 提取 groupID
|
|
|
|
|
|
let groupID = null;
|
|
|
|
|
|
if (conversationID.startsWith('GROUP')) {
|
|
|
|
|
|
groupID = conversationID.replace('GROUP', '');
|
|
|
|
|
|
} else if (this.conversation?.groupProfile?.groupID) {
|
|
|
|
|
|
groupID = this.conversation.groupProfile.groupID;
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!groupID) {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.error('无法获取群聊ID,conversationID:', conversationID);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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'
|
2026-01-22 16:35:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果是因为未登录导致的失败,尝试重连
|
|
|
|
|
|
if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) {
|
|
|
|
|
|
console.log('检测到未登录错误,尝试重连...');
|
|
|
|
|
|
this.isLoggedIn = false;
|
|
|
|
|
|
this.ensureIMConnection();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
return { success: false, error }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送自定义消息
|
|
|
|
|
|
async sendCustomMessage(messageData) {
|
|
|
|
|
|
if (!this.tim) {
|
|
|
|
|
|
this.triggerCallback('onError', 'IM未初始化')
|
2026-01-22 16:35:05 +08:00
|
|
|
|
return { success: false, error: 'IM未初始化' }
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 检查登录状态
|
|
|
|
|
|
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;
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
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;
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!groupID) {
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.error('无法获取群聊ID,conversationID:', conversationID);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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'
|
2026-01-22 16:35:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果是因为未登录导致的失败,尝试重连
|
|
|
|
|
|
if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) {
|
|
|
|
|
|
console.log('检测到未登录错误,尝试重连...');
|
|
|
|
|
|
this.isLoggedIn = false;
|
|
|
|
|
|
this.ensureIMConnection();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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',
|
2026-01-22 16:35:05 +08:00
|
|
|
|
avatar: timMessage.flow === 'in' ? '/static/home/avatar.svg' : '/static/center/user-avatar.png',
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 优先使用消息本身的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) {
|
2026-01-22 17:02:15 +08:00
|
|
|
|
// 支持 tempFilePath 或 path
|
2026-01-22 16:35:05 +08:00
|
|
|
|
if (imageFile?.tempFilePath) {
|
2026-01-22 17:02:15 +08:00
|
|
|
|
return imageFile.tempFilePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (imageFile?.path) {
|
|
|
|
|
|
return imageFile.path;
|
2026-01-22 16:35:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 处理字符串路径
|
2026-01-20 13:21:50 +08:00
|
|
|
|
if (typeof imageFile === 'string') {
|
2026-01-22 17:02:15 +08:00
|
|
|
|
return imageFile;
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.warn('无法获取图片URL,使用默认图片:', imageFile);
|
2026-01-22 17:02:15 +08:00
|
|
|
|
return '/static/home/photo.png';
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取图片尺寸信息
|
2026-01-22 17:02:15 +08:00
|
|
|
|
getImageInfo(imagePath) {
|
2026-01-20 13:21:50 +08:00
|
|
|
|
return new Promise((resolve) => {
|
2026-01-22 17:02:15 +08:00
|
|
|
|
// 如果传入的是对象,尝试提取路径
|
|
|
|
|
|
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);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
resolve({ width: 400, height: 300 });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 16:35:05 +08:00
|
|
|
|
console.log('获取图片信息,路径:', imagePath);
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 使用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资源 ===')
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清理所有定时器(使用统一方法)
|
|
|
|
|
|
this.clearAllTimers()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清理消息缓存(已弃用)
|
|
|
|
|
|
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()
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 清理其他状态
|
|
|
|
|
|
this.messageList = []
|
|
|
|
|
|
this.conversation = null
|
|
|
|
|
|
this.currentUserID = ''
|
|
|
|
|
|
this.currentUserSig = ''
|
|
|
|
|
|
this.currentConversationID = null
|
|
|
|
|
|
this.tim = null
|
2026-01-22 15:13:26 +08:00
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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);
|
2026-01-22 15:13:26 +08:00
|
|
|
|
return initPromise;
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
const clearInitIMPromise = () => {
|
|
|
|
|
|
initPromise = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取群聊列表(全局函数)
|
|
|
|
|
|
const getGroupList = async () => {
|
|
|
|
|
|
return await globalTimChatManager.getGroupList()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 进入群聊
|
2026-01-22 15:13:26 +08:00
|
|
|
|
const enterChatGroupRoom = async (chatGroup, navigateType = 'navigateTo', viewType) => {
|
2026-01-20 13:21:50 +08:00
|
|
|
|
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]({
|
2026-01-22 15:13:26 +08:00
|
|
|
|
url: `/pages-im/IM/index?conversationID=GROUP${chatGroup.groupID}&groupID=${chatGroup.groupID}&conversationType=GROUP&viewType=${viewType}`,
|
2026-01-20 13:21:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查全局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
|