diff --git a/.env.development b/.env.development index c35990f..88cc875 100644 --- a/.env.development +++ b/.env.development @@ -2,3 +2,4 @@ MP_API_BASE_URL=http://localhost:8080 MP_CACHE_PREFIX=development MP_WX_APP_ID=wx93af55767423938e MP_CORP_ID=wwe3fb2faa52cf9dfb +MP_TIM_SDK_APP_ID=1600123876 diff --git a/.env.localhost b/.env.localhost index 24011fd..29dbff8 100644 --- a/.env.localhost +++ b/.env.localhost @@ -1,3 +1,4 @@ MP_API_BASE_URL=http://192.168.60.2:8080 MP_CACHE_PREFIX=development MP_WX_APP_ID=wx93af55767423938e +MP_TIM_SDK_APP_ID=1600072268 diff --git a/App.vue b/App.vue index 32f7d93..7fe422c 100644 --- a/App.vue +++ b/App.vue @@ -1,5 +1,7 @@ diff --git a/index.html b/index.html index b5d330d..9e3eac7 100644 --- a/index.html +++ b/index.html @@ -1,20 +1,21 @@ - - - - - - - - -
- - - + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/pages.json b/pages.json index 86ee365..4e9ea8a 100644 --- a/pages.json +++ b/pages.json @@ -6,6 +6,13 @@ "navigationBarTitleText": "消息" } }, + { + "path": "pages/message/index", + "style": { + "navigationBarTitleText": "聊天", + "enablePullDownRefresh": false + } + }, { "path": "pages/case/case", "style": { diff --git a/pages/message/chat.scss b/pages/message/chat.scss index 15fdf23..62ece8f 100644 --- a/pages/message/chat.scss +++ b/pages/message/chat.scss @@ -1,4 +1,9 @@ - +// SCSS 变量定义 +$font-size-text: 28rpx; +$font-size-tip: 24rpx; +$font-size-title: 32rpx; +$text-color-sub: #999; +$primary-color: #0877F1; .chat-page { position: fixed; @@ -1231,5 +1236,4 @@ @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10rpx); } -} - +} \ No newline at end of file diff --git a/pages/message/components/chat-input.vue b/pages/message/components/chat-input.vue index 851e5b8..f103256 100644 --- a/pages/message/components/chat-input.vue +++ b/pages/message/components/chat-input.vue @@ -293,47 +293,8 @@ const cancelRecord = () => { stopRecordUtil(recorderManager); }; -// 发送病情描述消息 -const sendSymptomMessage = async () => { - const symptomMessage = createCustomMessage("symptom", { - content: "发送了病情描述", - symptomContent: "患者主诉:头痛、发热、咳嗽3天。既往史:无特殊。现病史:3天前开始出现头痛,伴有发热,体温最高38.5℃,同时有干咳症状。", - hasVisitedHospital: "yes", - selectedDiseases: [{ text: "感冒" }, { text: "上呼吸道感染" }], - images: ["/static/home/photo.png"], - }, props.formatTime); - await sendCustomMessage(symptomMessage); -}; -// 发送处方单消息 -const sendPrescriptionMessage = async () => { - const prescriptionMessage = createCustomMessage("prescription", { - content: "医生开了处方", - diagnosis: "上呼吸道感染", - medicines: [ - { name: "阿莫西林胶囊", spec: "0.25g×20粒", count: 2 }, - { name: "甲硝唑片", spec: "0.5mg×20片", count: 2 }, - ], - }, props.formatTime); - - await sendCustomMessage(prescriptionMessage); -}; - -// 发送续方申请消息 -const sendRefillMessage = async () => { - const refillMessage = createCustomMessage("refill", { - content: "发送了续方申请", - patientName: props.patientInfo.name, - gender: props.patientInfo.gender, - age: props.patientInfo.age, - diagnosis: "慢性胃炎,脾胃虚弱", - prescriptionType: "中药处方", - prescriptionDesc: "共14剂,每日1剂,1剂分2次服用,饭后温服", - }, props.formatTime); - - await sendCustomMessage(refillMessage); -}; // 发送问卷调查消息 const sendSurveyMessage = async () => { diff --git a/pages/message/components/head-card.vue b/pages/message/components/head-card.vue index f069f85..cc94bc4 100644 --- a/pages/message/components/head-card.vue +++ b/pages/message/components/head-card.vue @@ -12,7 +12,6 @@ 等待医生接诊..... - diff --git a/pages/message/components/message-types.vue b/pages/message/components/message-types.vue index 6794062..6b22ced 100644 --- a/pages/message/components/message-types.vue +++ b/pages/message/components/message-types.vue @@ -3,7 +3,6 @@ {{ message.payload.text }} - - - diff --git a/pages/message/index.vue b/pages/message/index.vue index 1fc6e9e..c84fce7 100644 --- a/pages/message/index.vue +++ b/pages/message/index.vue @@ -1,993 +1,592 @@ - - - - - - + + + + + diff --git a/pages/message/message.vue b/pages/message/message.vue index fb6b3e7..7e1d423 100644 --- a/pages/message/message.vue +++ b/pages/message/message.vue @@ -1,9 +1,501 @@ - - - - \ No newline at end of file + + diff --git a/store/account.js b/store/account.js index b25e21b..bbc9c94 100644 --- a/store/account.js +++ b/store/account.js @@ -2,7 +2,7 @@ import { ref } from "vue"; import { defineStore } from "pinia"; import api from '@/utils/api'; import { toast } from '@/utils/widget'; -import { getInitIMPromise, clearInitIMPromise } from "@/utils/tim-chat.js"; +import { initGlobalTIM, globalTimChatManager } from "@/utils/tim-chat.js"; const env = __VITE_ENV__; @@ -12,8 +12,10 @@ export default defineStore("accountStore", () => { const loading = ref(false) // IM 相关 const openid = ref(""); + const isIMInitialized = ref(false); // 医生信息 const doctorInfo = ref(null); + async function login(phoneCode = '') { if (loading.value) return; loading.value = true; @@ -38,6 +40,18 @@ export default defineStore("accountStore", () => { account.value = res.data; openid.value = res.data.openid; await getDoctorInfo(openid.value); + + // 登录成功后初始化腾讯IM + try { + console.log('开始初始化腾讯IM,userID:', res.data.openid); + await initGlobalTIM(res.data.openid); + isIMInitialized.value = true; + console.log('腾讯IM初始化成功'); + } catch (imError) { + console.error('腾讯IM初始化失败:', imError); + // IM初始化失败不影响登录流程 + } + return res.data } } @@ -47,6 +61,7 @@ export default defineStore("accountStore", () => { } loading.value = false } + async function getDoctorInfo() { try { const res = await api('getCorpMemberData', { @@ -60,5 +75,39 @@ export default defineStore("accountStore", () => { } } - return { account, openid, doctorInfo, login, getDoctorInfo } + async function initIMAfterLogin(userID) { + if (isIMInitialized.value) { + return true; + } + try { + await initGlobalTIM(userID); + isIMInitialized.value = true; + return true; + } catch (error) { + console.error('IM初始化失败:', error); + return false; + } + } + + // 退出登录 + async function logout() { + try { + // 退出腾讯IM + if (globalTimChatManager && globalTimChatManager.tim) { + console.log('开始退出腾讯IM'); + await globalTimChatManager.destroy(); + console.log('腾讯IM退出成功'); + } + } catch (error) { + console.error('退出腾讯IM失败:', error); + } + + // 清空账户信息 + account.value = null; + openid.value = ""; + isIMInitialized.value = false; + doctorInfo.value = null; + } + + return { account, openid, isIMInitialized, doctorInfo, login, getDoctorInfo, initIMAfterLogin, logout } }) \ No newline at end of file diff --git a/utils/api.js b/utils/api.js index beb4a37..f2164e7 100644 --- a/utils/api.js +++ b/utils/api.js @@ -28,6 +28,11 @@ const urlsConfig = { }, wecom: { addContactWay: 'addContactWay' + }, + im: { + getUserSig: 'getUserSig', + sendSystemMessage: "sendSystemMessage", + getChatRecordsByGroupId: "getChatRecordsByGroupId" } } diff --git a/utils/im-status-manager.js b/utils/im-status-manager.js index 7deb31a..7fc2ee3 100644 --- a/utils/im-status-manager.js +++ b/utils/im-status-manager.js @@ -1,8 +1,7 @@ -import { - checkGlobalIMStatus, +import { + checkGlobalIMStatus, ensureGlobalIMConnection, - getGlobalIMLoginStatus, - setGlobalIMCallback + getGlobalIMLoginStatus } from './tim-chat.js' /** @@ -68,10 +67,10 @@ class IMStatusManager { try { console.log('执行IM状态检查...') const isLoggedIn = checkGlobalIMStatus() - + // 触发状态变化回调 - this.triggerCallbacks('onStatusChange', { - isLoggedIn, + this.triggerCallbacks('onStatusChange', { + isLoggedIn, checkTime: now, timestamp: new Date().toLocaleString() }) @@ -97,26 +96,26 @@ class IMStatusManager { try { console.log('开始尝试IM重连...') const success = await ensureGlobalIMConnection() - + if (success) { console.log('IM重连成功') - this.triggerCallbacks('onReconnectSuccess', { - timestamp: new Date().toLocaleString() + this.triggerCallbacks('onReconnectSuccess', { + timestamp: new Date().toLocaleString() }) } else { console.log('IM重连失败') - this.triggerCallbacks('onReconnectFailed', { - timestamp: new Date().toLocaleString() + this.triggerCallbacks('onReconnectFailed', { + timestamp: new Date().toLocaleString() }) } return success } catch (error) { console.error('IM重连异常:', error) - this.triggerCallbacks('onReconnectFailed', { + this.triggerCallbacks('onReconnectFailed', { error, - timestamp: new Date().toLocaleString() + timestamp: new Date().toLocaleString() }) return false } @@ -206,7 +205,7 @@ class IMStatusManager { report: { isLoggedIn: status.isLoggedIn ? '已登录' : '未登录', monitoring: status.isMonitoring ? '监控中' : '未监控', - lastCheck: status.lastCheckTime ? + lastCheck: status.lastCheckTime ? new Date(status.lastCheckTime).toLocaleString() : '从未检查', interval: `${status.checkInterval / 1000}秒` } diff --git a/utils/tim-chat.js b/utils/tim-chat.js index 7682f0a..631aec1 100644 --- a/utils/tim-chat.js +++ b/utils/tim-chat.js @@ -1,13 +1,21 @@ // 引入腾讯IM SDK import TIM from 'tim-wx-sdk' import TIMUploadPlugin from 'tim-upload-plugin' -import { getUserSig, sendSystemMessage, getChatRecordsByGroupId } from '../api/corp/im.js' +import api from './api.js' const env = __VITE_ENV__; // 腾讯IM配置 - SDKAppID 必须是 number 类型,使用 Number() 转换 const TIM_CONFIG = { - SDKAppID: Number(env.APP_TIM_SDK_APP_ID), // 患者端 IM SDKAppID + SDKAppID: Number(env.MP_TIM_SDK_APP_ID), // 患者端 IM SDKAppID +} + +// 验证配置 +if (!TIM_CONFIG.SDKAppID || isNaN(TIM_CONFIG.SDKAppID)) { + console.error('❌ TIM SDK配置错误: MP_TIM_SDK_APP_ID未定义或无效', { + envValue: env.MP_TIM_SDK_APP_ID, + parsedValue: TIM_CONFIG.SDKAppID + }) } // IM连接配置常量 @@ -33,38 +41,38 @@ class TimChatManager { // TIM实例和会话 this.tim = null this.conversation = null - + // 用户信息 this.currentUserID = '' this.currentUserSig = '' - + // 消息管理 this.messageList = [] this.currentConversationID = null - + // 分页加载相关状态 this.nextReqMessageID = "" this.isCompleted = false this.isLoadingMore = false - + // 状态标志 this.isLoading = false this.isLoggedIn = false this.isInitializing = false this.isLoggingIn = false - + // 定时器 this.loginCheckInterval = null this.heartbeatInterval = null this.networkReconnectTimer = null - + // 重连管理 this.reconnectAttempts = 0 this.maxReconnectAttempts = IM_CONNECTION_CONFIG.MAX_RECONNECT_ATTEMPTS this.reconnectDelays = IM_CONNECTION_CONFIG.RECONNECT_DELAYS this.lastLoginTime = 0 this.loginCooldown = IM_CONNECTION_CONFIG.LOGIN_COOLDOWN - + // 心跳管理 this.heartbeatFailCount = 0 @@ -79,21 +87,21 @@ class TimChatManager { onConversationListUpdated: null } - // 绑定事件处理函数 - this.boundEventHandlers = { - onSDKReady: this.onSDKReady.bind(this), - onSDKNotReady: this.onSDKNotReady.bind(this), - onMessageReceived: this.onMessageReceived.bind(this), - onMessageSentSucceeded: this.onMessageSentSucceeded.bind(this), - onMessageSentFailed: this.onMessageSentFailed.bind(this), - onConversationListUpdated: this.onConversationListUpdated.bind(this), - onNetStateChange: this.onNetStateChange.bind(this), - onKickedOut: this.onKickedOut.bind(this) - } + // 绑定事件处理函数 + this.boundEventHandlers = { + onSDKReady: this.onSDKReady.bind(this), + onSDKNotReady: this.onSDKNotReady.bind(this), + onMessageReceived: this.onMessageReceived.bind(this), + onMessageSentSucceeded: this.onMessageSentSucceeded.bind(this), + onMessageSentFailed: this.onMessageSentFailed.bind(this), + onConversationListUpdated: this.onConversationListUpdated.bind(this), + onNetStateChange: this.onNetStateChange.bind(this), + onKickedOut: this.onKickedOut.bind(this) + } } // ============== 资源管理方法 ============== - + // 清理所有定时器 clearAllTimers() { if (this.loginCheckInterval) { @@ -101,25 +109,25 @@ class TimChatManager { clearInterval(this.loginCheckInterval) this.loginCheckInterval = null } - + if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval) this.heartbeatInterval = null } - + if (this.networkReconnectTimer) { clearTimeout(this.networkReconnectTimer) this.networkReconnectTimer = null } - + console.log('所有定时器已清理') } - + // 清理缓存(已弃用缓存功能) cleanupCache() { // 缓存功能已移除 } - + // 重置所有状态 resetAllStates() { this.isLoggedIn = false @@ -131,7 +139,7 @@ class TimChatManager { } // ============== 初始化方法 ============== - + // 初始化腾讯IM async initTIM(userID = null) { if (this.isInitializing) { @@ -145,7 +153,7 @@ class TimChatManager { try { // 重置重连次数,允许重新登录 this.reconnectAttempts = 0 - + // 如果存在旧的TIM实例,先完整清理 if (this.tim) { console.log('检测到旧的TIM实例,开始清理...') @@ -155,8 +163,12 @@ class TimChatManager { if (!TIM) { throw new Error('TIM SDK 未正确导入') } + // 验证 SDKAppID + if (!TIM_CONFIG.SDKAppID || isNaN(TIM_CONFIG.SDKAppID)) { + throw new Error(`TIM SDK配置错误: SDKAppID无效 (${TIM_CONFIG.SDKAppID}),请检查环境变量 MP_TIM_SDK_APP_ID`) + } - + console.log('创建TIM实例,SDKAppID:', TIM_CONFIG.SDKAppID) this.tim = TIM.create({ SDKAppID: TIM_CONFIG.SDKAppID }) // 等待TIM实例初始化完成 @@ -187,7 +199,7 @@ class TimChatManager { } catch (error) { console.error('=== IM初始化失败 ===', error) this.triggerCallback('onError', `初始化失败: ${error.message || error}`) - + // 初始化失败时清理资源 await this.cleanupOldInstance() return false @@ -195,16 +207,16 @@ class TimChatManager { this.isInitializing = false } } - + // 清理旧的TIM实例 async cleanupOldInstance() { try { // 清理所有定时器 this.clearAllTimers() - + // 移除事件监听器 this.removeEventListeners() - + // 登出 if (this.tim && this.tim.isLoggedIn) { try { @@ -214,25 +226,25 @@ class TimChatManager { console.warn('登出旧实例失败:', err) } } - + // 清理实例 this.tim = null - + // 重置状态 this.resetAllStates() - + console.log('旧实例清理完成') } catch (error) { console.error('清理旧实例时出错:', error) } } - + // 等待TIM实例准备就绪 waitForTIMInstanceReady() { return new Promise((resolve, reject) => { const startTime = Date.now() const timeout = 10000 // 10秒超时 - + const checkReady = () => { if (this.tim && typeof this.tim.on === 'function') { console.log('TIM实例已就绪') @@ -243,7 +255,7 @@ class TimChatManager { setTimeout(checkReady, IM_CONNECTION_CONFIG.TIM_INSTANCE_READY_CHECK_INTERVAL) } } - + checkReady() }) } @@ -258,7 +270,7 @@ class TimChatManager { const checkSDKReady = () => { checkCount++ const elapsed = Date.now() - startTime - + if (this.isLoggedIn) { console.log(`✓ SDK已Ready(耗时${elapsed}ms,检查${checkCount}次)`) resolve() @@ -268,7 +280,7 @@ class TimChatManager { // 超时不算致命错误,尝试继续 resolve() } else { - console.log(`等待SDK Ready... ${Math.floor(elapsed/1000)}/${Math.floor(timeout/1000)}秒`) + console.log(`等待SDK Ready... ${Math.floor(elapsed / 1000)}/${Math.floor(timeout / 1000)}秒`) setTimeout(checkSDKReady, checkInterval) } } @@ -290,17 +302,17 @@ class TimChatManager { return } - // 逐个注册事件监听器,如果某个注册失败则继续注册其他的 - const events = [ - { event: TIM.EVENT.SDK_READY, handler: this.boundEventHandlers.onSDKReady }, - { event: TIM.EVENT.SDK_NOT_READY, handler: this.boundEventHandlers.onSDKNotReady }, - { event: TIM.EVENT.MESSAGE_RECEIVED, handler: this.boundEventHandlers.onMessageReceived }, - { event: TIM.EVENT.MESSAGE_SENT_SUCCEEDED, handler: this.boundEventHandlers.onMessageSentSucceeded }, - { event: TIM.EVENT.MESSAGE_SENT_FAILED, handler: this.boundEventHandlers.onMessageSentFailed }, - { event: TIM.EVENT.CONVERSATION_LIST_UPDATED, handler: this.boundEventHandlers.onConversationListUpdated }, - { event: TIM.EVENT.NET_STATE_CHANGE, handler: this.boundEventHandlers.onNetStateChange }, - { event: TIM.EVENT.KICKED_OUT, handler: this.boundEventHandlers.onKickedOut } - ] + // 逐个注册事件监听器,如果某个注册失败则继续注册其他的 + const events = [ + { event: TIM.EVENT.SDK_READY, handler: this.boundEventHandlers.onSDKReady }, + { event: TIM.EVENT.SDK_NOT_READY, handler: this.boundEventHandlers.onSDKNotReady }, + { event: TIM.EVENT.MESSAGE_RECEIVED, handler: this.boundEventHandlers.onMessageReceived }, + { event: TIM.EVENT.MESSAGE_SENT_SUCCEEDED, handler: this.boundEventHandlers.onMessageSentSucceeded }, + { event: TIM.EVENT.MESSAGE_SENT_FAILED, handler: this.boundEventHandlers.onMessageSentFailed }, + { event: TIM.EVENT.CONVERSATION_LIST_UPDATED, handler: this.boundEventHandlers.onConversationListUpdated }, + { event: TIM.EVENT.NET_STATE_CHANGE, handler: this.boundEventHandlers.onNetStateChange }, + { event: TIM.EVENT.KICKED_OUT, handler: this.boundEventHandlers.onKickedOut } + ] events.forEach(({ event, handler }) => { if (event && handler && typeof this.tim.on === 'function') { @@ -377,9 +389,9 @@ class TimChatManager { // 获取 userSig async getUserSig(userID) { try { - const response = await getUserSig(userID) - if (response?.success && response?.data?.userSig) { - return response.data.userSig + const response = await api('getUserSig', { userId: userID }) + if (response?.success && response?.data) { + return response.data } throw new Error('获取 userSig 失败: 接口返回数据格式错误') } catch (error) { @@ -417,25 +429,25 @@ class TimChatManager { }).then(() => { console.log('腾讯IM登录成功') this.isLoggingIn = false - this.isLoggedIn = true + this.isLoggedIn = true this.reconnectAttempts = 0 - + // 启动心跳检测 this.startHeartbeat() - + // 启动登录状态检测 this.startLoginStatusCheck() - + // 触发登录状态变化回调 this.triggerCallback('onLoginStatusChanged', { isLoggedIn: true, userID: this.currentUserID, reason: 'LOGIN_SUCCESS' }) - + // 获取会话列表(确保连接正常) this.getConversationList() - + resolve() }).catch(error => { console.error('腾讯IM登录失败:', error) @@ -451,11 +463,11 @@ class TimChatManager { } // ============== 连接监控方法 ============== - + // 启动登录状态检测(优化版:使用配置常量) startLoginStatusCheck() { this.stopLoginStatusCheck() - + // 根据连接稳定性动态调整检查间隔 const getCheckInterval = () => { if (this.reconnectAttempts > 0) { @@ -463,22 +475,22 @@ class TimChatManager { } return IM_CONNECTION_CONFIG.LOGIN_CHECK_INTERVAL_STABLE } - + const checkStatus = () => { // 如果已达到最大重连次数(被踢下线),停止检查 if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('⚠️ 已被踢下线或达到最大重连次数,停止登录状态检查') return } - + // 如果正在登录中,跳过本次检查 if (this.isLoggingIn) { this.loginCheckInterval = setTimeout(checkStatus, getCheckInterval()) return } - + const isLoggedIn = this.checkLoginStatus() - + // 如果未登录且有用户ID,尝试重连 if (!isLoggedIn && this.currentUserID && !this.isLoggingIn) { console.log('📡 登录状态检测:发现未登录,尝试重连') @@ -489,21 +501,21 @@ class TimChatManager { console.log('✓ 登录状态检测:连接已恢复正常') this.reconnectAttempts = 0 } - + // 定期清理缓存 this.cleanupCache() } - + // 重新调度下一次检查(再次检查,防止在检查过程中被踢下线) if (this.reconnectAttempts < this.maxReconnectAttempts) { this.loginCheckInterval = setTimeout(checkStatus, getCheckInterval()) } } - + // 首次检查延迟 const firstDelay = IM_CONNECTION_CONFIG.LOGIN_CHECK_FIRST_DELAY this.loginCheckInterval = setTimeout(checkStatus, firstDelay) - console.log(`🔍 登录状态检测已启动,首次检查将在${firstDelay/1000}秒后进行`) + console.log(`🔍 登录状态检测已启动,首次检查将在${firstDelay / 1000}秒后进行`) } // 停止登录状态检测 @@ -525,7 +537,7 @@ class TimChatManager { if (this.isLoggedIn) { // 只在已登录状态下,通过 SDK 再次验证 let sdkLoginStatus = true // 默认保持登录状态 - + if (typeof this.tim.getLoginStatus === 'function') { try { const status = this.tim.getLoginStatus() @@ -539,7 +551,7 @@ class TimChatManager { // 出错时保持当前状态 } } - + // 只有 SDK 明确返回未登录时才更新状态 if (!sdkLoginStatus) { this.isLoggedIn = false @@ -550,7 +562,7 @@ class TimChatManager { }) return false } - + // 保持登录状态 return true } @@ -607,19 +619,19 @@ class TimChatManager { const delayIndex = Math.min(this.reconnectAttempts, this.reconnectDelays.length - 1) const reconnectDelay = this.reconnectDelays[delayIndex] const timeSinceLastLogin = now - this.lastLoginTime - + // 只有非首次重连才需要检查冷却时间 if (this.reconnectAttempts > 0 && timeSinceLastLogin < reconnectDelay) { const remainingTime = reconnectDelay - timeSinceLastLogin console.log(`重连冷却中,剩余时间:${Math.ceil(remainingTime / 1000)}秒`) - + // 安排下次重连 setTimeout(() => { if (!this.isLoggedIn && !this.isLoggingIn && this.reconnectAttempts < this.maxReconnectAttempts) { this.attemptReconnect() } }, remainingTime) - + return false } @@ -652,7 +664,7 @@ class TimChatManager { return false } catch (error) { console.error(`第${this.reconnectAttempts}次重连失败:`, error) - + // 如果还有重连机会,自动安排下一次重连 if (this.reconnectAttempts < this.maxReconnectAttempts) { const nextDelay = this.reconnectDelays[Math.min(this.reconnectAttempts, this.reconnectDelays.length - 1)] @@ -663,7 +675,7 @@ class TimChatManager { } }, nextDelay) } - + return false } } @@ -723,26 +735,26 @@ class TimChatManager { // 缓存功能已移除 - // 判断是否为当前会话的消息(必须有currentConversationID且匹配才显示) - const isCurrentConversation = this.currentConversationID && - messageConversationID === this.currentConversationID + // 判断是否为当前会话的消息(必须有currentConversationID且匹配才显示) + const isCurrentConversation = this.currentConversationID && + messageConversationID === this.currentConversationID - console.log('消息会话匹配检查:', { - isCurrentConversation, - hasCurrentConversationID: !!this.currentConversationID, - conversationIDMatch: messageConversationID === this.currentConversationID - }) + console.log('消息会话匹配检查:', { + isCurrentConversation, + hasCurrentConversationID: !!this.currentConversationID, + conversationIDMatch: messageConversationID === this.currentConversationID + }) - if (isCurrentConversation) { - // 当前会话的消息,触发回调 - console.log('✓ 消息属于当前会话,触发显示') - this.triggerCallback('onMessageReceived', convertedMessage) + if (isCurrentConversation) { + // 当前会话的消息,触发回调 + console.log('✓ 消息属于当前会话,触发显示') + this.triggerCallback('onMessageReceived', convertedMessage) // 处理已读状态 if (this.currentConversationID) { this.markConversationAsRead(this.currentConversationID) } - + this.triggerCallback('onConversationListUpdated', { reason: 'NEW_MESSAGE_RECEIVED_IN_CURRENT_CONVERSATION', conversation: { @@ -770,7 +782,7 @@ class TimChatManager { onMessageSentSucceeded(event) { const messageID = event.data.ID const sentMessage = event.data.message - + // 更新messageList中的消息状态 const message = this.messageList.find(msg => msg.ID === messageID) if (message) { @@ -792,7 +804,7 @@ class TimChatManager { // 消息发送失败 onMessageSentFailed(event) { const messageID = event.data.ID - + // 更新messageList中的消息状态 const message = this.messageList.find(msg => msg.ID === messageID) if (message) { @@ -816,18 +828,18 @@ class TimChatManager { // 网络状态变化(优化版:更稳定的处理) onNetStateChange(event) { const netState = event.data.netState - + console.log('🌐 网络状态变化:', netState) - + // 清理之前的网络重连定时器 if (this.networkReconnectTimer) { clearTimeout(this.networkReconnectTimer) this.networkReconnectTimer = null } - + if (netState === TIM.TYPES.NET_STATE_CONNECTED) { console.log('✓ 网络已连接,延迟检查IM状态以确保稳定') - + // 网络恢复后延迟再检查,避免网络还不稳定时立即重连 const delay = IM_CONNECTION_CONFIG.NETWORK_RECONNECT_DELAY this.networkReconnectTimer = setTimeout(() => { @@ -839,22 +851,22 @@ class TimChatManager { } this.networkReconnectTimer = null }, delay) - + // 重置重连次数(网络恢复后给更多机会) if (this.reconnectAttempts > 0) { console.log(`重置重连次数(之前: ${this.reconnectAttempts})`) this.reconnectAttempts = 0 } - + } else if (netState === TIM.TYPES.NET_STATE_CONNECTING) { console.log('🔗 网络连接中,等待稳定...') - + } else if (netState === TIM.TYPES.NET_STATE_DISCONNECTED) { console.log('⚠️ 网络断开(暂不标记为未登录,等待心跳检测判断)') - + // 网络断开时不立即设置 isLoggedIn = false // 因为可能只是短暂的网络波动,让心跳检测来判断是否真的断线 - + this.triggerCallback('onLoginStatusChanged', { isLoggedIn: this.isLoggedIn, reason: 'NETWORK_DISCONNECTED', @@ -866,26 +878,26 @@ class TimChatManager { // 被踢下线处理 onKickedOut(event) { console.log('⚠️ 账号被踢下线:', event.data) - + // 更新登录状态 this.isLoggedIn = false this.isLoggingIn = false - + // 停止所有定时器(防止自动重连) this.clearAllTimers() this.stopLoginStatusCheck() this.stopHeartbeat() - + // 清除重连相关状态,阻止重连 this.reconnectAttempts = this.maxReconnectAttempts // 设置为最大值,阻止重连 - + // 触发登录状态变化回调 this.triggerCallback('onLoginStatusChanged', { isLoggedIn: false, reason: 'KICKED_OUT', message: '您的账号在其他设备登录' }) - + // 显示提示弹框 uni.showModal({ title: '提示', @@ -899,7 +911,7 @@ class TimChatManager { uni.removeStorageSync('account') uni.removeStorageSync('openid') uni.reLaunch({ - url: '/pages/login/login' + url: '/pages-center/login/login' }) } }) @@ -919,12 +931,12 @@ class TimChatManager { // 启动心跳检测(优化版:使用配置常量) startHeartbeat() { this.stopHeartbeat() - + // 心跳失败计数器 this.heartbeatFailCount = 0 const MAX_HEARTBEAT_FAIL = IM_CONNECTION_CONFIG.HEARTBEAT_MAX_FAIL const INTERVAL = IM_CONNECTION_CONFIG.HEARTBEAT_INTERVAL - + // 定时心跳检测 this.heartbeatInterval = setInterval(() => { // 如果已达到最大重连次数(被踢下线),停止心跳检测 @@ -933,13 +945,13 @@ class TimChatManager { this.stopHeartbeat() return } - + // 只在已登录状态下进行心跳检测 if (!this.tim || !this.isLoggedIn) { console.log('⏸ 心跳检测:未登录,跳过检测') return } - + this.tim.getConversationList() .then(() => { if (this.heartbeatFailCount > 0) { @@ -950,7 +962,7 @@ class TimChatManager { .catch((error) => { this.heartbeatFailCount++ console.error(`💔 心跳失败 (${this.heartbeatFailCount}/${MAX_HEARTBEAT_FAIL}):`, error.message) - + // 只有连续失败多次才认为真的断线 if (this.heartbeatFailCount >= MAX_HEARTBEAT_FAIL) { console.log('❌ 心跳连续失败,标记为未登录并尝试重连') @@ -963,8 +975,8 @@ class TimChatManager { } }) }, INTERVAL) - - console.log(`💓 心跳检测已启动(间隔${INTERVAL/1000}秒,最多失败${MAX_HEARTBEAT_FAIL}次)`) + + console.log(`💓 心跳检测已启动(间隔${INTERVAL / 1000}秒,最多失败${MAX_HEARTBEAT_FAIL}次)`) } // 停止心跳检测 @@ -982,7 +994,7 @@ class TimChatManager { console.error('TIM实例不存在,无法获取会话列表') return } - + if (!this.isLoggedIn) { console.log('SDK未ready,等待SDK初始化后再获取会话列表...') // 等待SDK就绪 @@ -997,7 +1009,7 @@ class TimChatManager { setTimeout(checkSDKReady, 500) return } - + this.tim.getConversationList({ withGroupInfo: 1, withAllFields: 1 }).then(response => { if (this.conversationID) { this.enterConversation(this.conversationID) @@ -1017,15 +1029,24 @@ class TimChatManager { if (!this.isLoggedIn) { console.log('SDK未ready,等待SDK初始化...') + let waitTime = 0 + const maxWaitTime = 30000 // 最多等待30秒 + const checkInterval = 1000 // 每秒检查一次 + const checkSDKReady = () => { if (this.isLoggedIn) { console.log('SDK已ready,开始获取群聊列表') this.getGroupListInternal().then(resolve).catch(reject) + } else if (waitTime >= maxWaitTime) { + console.error('等待SDK就绪超时') + reject(new Error('SDK初始化超时,请检查网络连接')) } else { - console.log('SDK仍未ready,继续等待...') - setTimeout(checkSDKReady, 1000) + waitTime += checkInterval + console.log(`等待SDK就绪... (${Math.floor(waitTime / 1000)}/${Math.floor(maxWaitTime / 1000)}秒)`) + setTimeout(checkSDKReady, checkInterval) } } + checkSDKReady() return } @@ -1039,7 +1060,15 @@ class TimChatManager { return new Promise((resolve, reject) => { console.log('开始获取群聊列表') - this.tim.getConversationList().then(async (conversationResponse) => { + // 确保SDK已就绪再调用getConversationList + const ensureSDKReady = () => { + if (!this.isLoggedIn) { + console.log('SDK未就绪,等待中...') + setTimeout(ensureSDKReady, 500) + return + } + + this.tim.getConversationList().then(async (conversationResponse) => { console.clear() console.log('获取会话列表成功:', conversationResponse) const groupConversations = conversationResponse.data.conversationList.filter(conversation => { @@ -1121,7 +1150,7 @@ class TimChatManager { name: conversation.groupProfile.name, doctorId, patientName, - avatar: '/static/home/doctor.png', + avatar: '/static/home/avatar.svg', lastMessage: '获取失败', lastMessageTime: Date.now(), unreadCount: conversation.unreadCount || 0, @@ -1145,6 +1174,10 @@ class TimChatManager { error: imError }) }) + } + + // 开始检查SDK就绪状态 + ensureSDKReady() }) } @@ -1264,7 +1297,7 @@ class TimChatManager { console.log('创建问诊群聊成功:', imResponse) try { - await sendSystemMessage(groupID, 'pending') + await api('sendSystemMessage', { groupID, status: 'pending' }) console.log('pending系统消息发送成功') } catch (error) { console.error('pending系统消息发送失败:', error) @@ -1360,20 +1393,20 @@ class TimChatManager { // 进入会话 enterConversation(conversationID) { console.log("【enterConversation】进入会话:", conversationID) - + // 更新当前会话ID this.currentConversationID = conversationID - + // 清空当前消息列表 this.messageList = [] - + // 重置分页状态 this.nextReqMessageID = "" this.isCompleted = false this.isLoadingMore = false - + console.log(" 会话ID已更新,消息列表已清空,分页状态已重置") - + // 进入群聊会话,默认加载20条消息 this.enterGroupConversation(conversationID, 20) } @@ -1381,7 +1414,7 @@ class TimChatManager { // 进入群聊会话 async enterGroupConversation(groupID, count = 20) { console.log("【enterGroupConversation】进入群聊会话, groupID:", groupID, "count:", count) - + let conversationID = groupID let actualGroupID = groupID @@ -1424,30 +1457,28 @@ class TimChatManager { try { console.log("【loadMessagesFromLocalAPI】开始从本地API加载聊天记录") console.log(" groupID:", groupID, "count:", count, "skip:", skip, "isPullUp:", isPullUp) - + // 调用本地接口获取聊天记录 - const response = await getChatRecordsByGroupId(groupID, count, skip) - - console.log(" 📥 收到本地API响应:", response) - + const response = await api('getChatRecordsByGroupId', { groupID, count, skip }) + if (response && response.success && response.data && response.data.records) { const dbMessages = response.data.records const hasMore = response.data.hasMore || false // 从后端获取hasMore字段 const total = response.data.total || 0 - + console.log(` 成功获取 ${dbMessages.length} 条聊天记录`) console.log(` hasMore: ${hasMore}, total: ${total}`) - + // 将数据库消息转换为IM消息格式 const convertedMessages = dbMessages .map(dbMsg => this.convertDBMessageToIMFormat(dbMsg)) .filter(msg => msg !== null && this.filterMessage(msg)) - + console.log(` 转换后 ${convertedMessages.length} 条消息`) - + // 按时间排序(从早到晚) convertedMessages.sort((a, b) => a.lastTime - b.lastTime) - + // 根据是否为上拉加载,决定如何更新消息列表 if (isPullUp) { // 上拉加载更多:将新消息插入到列表前面(历史消息) @@ -1458,12 +1489,12 @@ class TimChatManager { // 首次加载:直接替换消息列表 this.messageList = convertedMessages } - + // 设置分页状态 - 使用后端返回的 hasMore 字段 this.isCompleted = !hasMore - + console.log(` 分页状态: isCompleted=${this.isCompleted}, hasMore=${hasMore}`) - + // 触发回调 this.triggerCallback("onMessageListLoaded", { messages: this.messageList, @@ -1472,7 +1503,7 @@ class TimChatManager { isCompleted: this.isCompleted, total: total, }) - + return { success: true, count: convertedMessages.length, @@ -1499,22 +1530,22 @@ class TimChatManager { } } } - + // 将数据库消息转换为IM消息格式 convertDBMessageToIMFormat(dbMsg) { try { if (!dbMsg || !dbMsg.MsgBody || !Array.isArray(dbMsg.MsgBody) || dbMsg.MsgBody.length === 0) { return null } - + // 获取第一个消息体(通常一个消息只有一个消息体) const msgBody = dbMsg.MsgBody[0] const msgType = msgBody.MsgType - + // 确定消息的流向(in/out) // 根据 From_Account 判断是否为当前用户发送的消息 const flow = dbMsg.From_Account === this.currentUserID ? 'out' : 'in' - + // 计算时间戳(毫秒) let lastTime = Date.now() if (dbMsg.MsgTime) { @@ -1523,7 +1554,7 @@ class TimChatManager { } else if (dbMsg.createdAt) { lastTime = new Date(dbMsg.createdAt).getTime() } - + // 构建基础消息对象 const message = { ID: dbMsg.MsgSeq || dbMsg._id || `db_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, @@ -1537,46 +1568,46 @@ class TimChatManager { conversationID: this.currentConversationID, MsgSeq: dbMsg.MsgSeq, // 保留 MsgSeq 用于分页 } - + return message } catch (error) { console.error("转换数据库消息格式失败:", error, dbMsg) return null } } - + // 将数据库消息体转换为IM payload格式 convertDBPayloadToIMPayload(msgType, msgContent) { try { if (!msgContent) { return {} } - + switch (msgType) { case 'TIMTextElem': return { text: msgContent.Text || '' } - + case 'TIMImageElem': return { imageInfoArray: msgContent.ImageInfoArray || [] } - + case 'TIMSoundElem': return { url: msgContent.Url || '', second: msgContent.Second || 0, downloadFlag: msgContent.Download_Flag || 2 } - + case 'TIMCustomElem': return { data: msgContent.Data || '', description: msgContent.Desc || '', extension: msgContent.Ext || '' } - + case 'TIMVideoFileElem': return { videoUrl: msgContent.VideoUrl || '', @@ -1591,7 +1622,7 @@ class TimChatManager { thumbFormat: msgContent.ThumbFormat || '', thumbDownloadFlag: msgContent.ThumbDownloadFlag || 2 } - + case 'TIMFileElem': return { url: msgContent.Url || '', @@ -1599,7 +1630,7 @@ class TimChatManager { fileName: msgContent.FileName || '', downloadFlag: msgContent.Download_Flag || 2 } - + default: return msgContent } @@ -1641,7 +1672,7 @@ class TimChatManager { const messageList = response.data.messageList const oldNextReqMessageID = this.nextReqMessageID const oldIsCompleted = this.isCompleted - + this.nextReqMessageID = response.data.nextReqMessageID this.isCompleted = response.data.isCompleted @@ -1717,7 +1748,7 @@ class TimChatManager { // 从本地数据库加载更多历史消息 // 使用当前消息列表的长度作为 skip 值 const skip = this.messageList.length - + // 获取群组ID let groupID = '' if (this.conversation && this.conversation.groupProfile) { @@ -1725,7 +1756,7 @@ class TimChatManager { } else if (this.currentConversationID) { groupID = this.currentConversationID.replace('GROUP', '') } - + if (!groupID) { console.error(" ❌ 无法获取群组ID") this.isLoadingMore = false @@ -1734,12 +1765,12 @@ class TimChatManager { message: "无法获取群组ID", } } - + console.log(" 从本地数据库加载更多,groupID:", groupID, "skip:", skip) - + // 调用本地API加载更多消息 const result = await this.loadMessagesFromLocalAPI(groupID, 20, skip, true) - + this.isLoadingMore = false console.log(" 🔄 设置 isLoadingMore = false") console.log(" 📥 loadMessagesFromLocalAPI 返回结果:", result) @@ -1778,11 +1809,11 @@ class TimChatManager { if (result.success) { // 严格过滤:只保留属于当前会话的消息 const filteredMessages = result.messages.filter(msg => msg.conversationID === conversationID) - + this.messageList = filteredMessages - + console.log(`消息刷新完成,过滤后${filteredMessages.length}条消息`) - + // 触发更新回调 this.triggerCallback('onMessageListLoaded', { messages: this.messageList, @@ -1800,14 +1831,14 @@ class TimChatManager { // 合并消息(保留本地消息,避免覆盖) mergeMessages(serverMessages, cachedMessages) { const messageMap = new Map() - + // 先添加服务器消息 serverMessages.forEach(msg => { if (msg.ID) { messageMap.set(msg.ID, msg) } }) - + // 再添加缓存消息(覆盖服务器消息,保留本地最新状态) cachedMessages.forEach(msg => { if (msg.ID) { @@ -1820,13 +1851,13 @@ class TimChatManager { } } }) - + // 转换为数组并按时间排序 const mergedArray = Array.from(messageMap.values()) mergedArray.sort((a, b) => a.lastTime - b.lastTime) - + console.log(`消息合并完成: 服务器${serverMessages.length}条, 缓存${cachedMessages.length}条, 合并后${mergedArray.length}条`) - + return mergedArray } @@ -1963,7 +1994,7 @@ class TimChatManager { // 使用Set去重,避免重复消息 const existingIds = new Set(allMessages.map(m => m.ID)) const newMessages = result.messages.filter(m => !existingIds.has(m.ID)) - + if (newMessages.length > 0) { allMessages.unshift(...newMessages) console.log(`添加${newMessages.length}条新消息,当前总数: ${allMessages.length}`) @@ -2499,10 +2530,10 @@ class TimChatManager { // 清理资源(优化版:更完整的清理) destroy() { console.log('=== 开始清理IM资源 ===') - + // 清理所有定时器(使用统一方法) this.clearAllTimers() - + // 清理消息缓存(已弃用) console.log('缓存功能已移除') @@ -2525,7 +2556,7 @@ class TimChatManager { // 重置所有状态(使用统一方法) this.resetAllStates() - + // 清理其他状态 this.messageList = [] this.conversation = null @@ -2533,7 +2564,7 @@ class TimChatManager { this.currentUserSig = '' this.currentConversationID = null this.tim = null - + console.log('=== IM资源清理完成 ===') } } @@ -2579,7 +2610,7 @@ const initGlobalTIM = async (userID, forceReinit = false) => { const getInitIMPromise = async (userID, forceReinit) => { if (initPromise) return initPromise; initPromise = initGlobalTIM(userID, forceReinit); - return initPromise; + return initPromise; } const clearInitIMPromise = () => { initPromise = null; @@ -2591,14 +2622,14 @@ const getGroupList = async () => { } // 进入群聊 -const enterChatGroupRoom = async (chatGroup, navigateType = 'navigateTo' , viewType) => { +const enterChatGroupRoom = async (chatGroup, navigateType = 'navigateTo', viewType) => { const groupListResult = await globalTimChatManager.getGroupList(); const groupList = groupListResult && Array.isArray(groupListResult.groupList) ? groupListResult.groupList : []; let group = groupList.find(g => g.groupID === chatGroup.groupID); const navigateFn = ['navigateTo', 'redirectTo', 'reLaunch'].includes(navigateType) ? navigateType : 'navigateTo' uni[navigateFn]({ - url: `/pages/message/index?conversationID=GROUP${chatGroup.groupID}&groupID=${chatGroup.groupID}&conversationType=GROUP&viewType=${viewType}`, + url: `/pages-im/IM/index?conversationID=GROUP${chatGroup.groupID}&groupID=${chatGroup.groupID}&conversationType=GROUP&viewType=${viewType}`, }) }