From 16c7d2b261ff65c3e78a15688e004a5c44fb08cc Mon Sep 17 00:00:00 2001 From: wangdongbo <949818794@qq.com> Date: Thu, 22 Jan 2026 16:35:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E9=97=B4=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 18 +- pages/message/chat.scss | 20 +- pages/message/components/chat-input.vue | 30 +- pages/message/components/head-card.vue | 2 +- pages/message/index.vue | 45 ++- pages/message/message.vue | 84 +++-- static/icon/changyongyu.png | Bin 0 -> 2793 bytes static/icon/fuzhenyuyue.png | Bin 0 -> 3537 bytes static/icon/icon-chinese-rx.png | Bin 0 -> 4406 bytes static/icon/icon-western-rx.png | Bin 0 -> 3880 bytes static/icon/jieshuzixun.png | Bin 0 -> 4352 bytes static/icon/kaichufang.png | Bin 0 -> 1346 bytes static/icon/kaiyizhu.png | Bin 0 -> 2637 bytes static/icon/kaizhongyao.png | Bin 0 -> 1288 bytes static/icon/paizhao.png | Bin 0 -> 3663 bytes static/icon/quxiaobingtuikuan.png | Bin 0 -> 3930 bytes static/icon/xuanjiaowenzhang.png | Bin 0 -> 2708 bytes static/icon/zhaopian.png | Bin 0 -> 2922 bytes static/icon/zhenliaoyijian.png | Bin 0 -> 3057 bytes store/account.js | 14 +- utils/chat-utils.js | 5 + utils/tim-chat.js | 478 ++++++++++++++---------- 22 files changed, 429 insertions(+), 267 deletions(-) create mode 100644 static/icon/changyongyu.png create mode 100644 static/icon/fuzhenyuyue.png create mode 100644 static/icon/icon-chinese-rx.png create mode 100644 static/icon/icon-western-rx.png create mode 100644 static/icon/jieshuzixun.png create mode 100644 static/icon/kaichufang.png create mode 100644 static/icon/kaiyizhu.png create mode 100644 static/icon/kaizhongyao.png create mode 100644 static/icon/paizhao.png create mode 100644 static/icon/quxiaobingtuikuan.png create mode 100644 static/icon/xuanjiaowenzhang.png create mode 100644 static/icon/zhaopian.png create mode 100644 static/icon/zhenliaoyijian.png diff --git a/App.vue b/App.vue index 7fe422c..bffc016 100644 --- a/App.vue +++ b/App.vue @@ -15,15 +15,15 @@ export default { onHide: function () { console.log("App Hide"); // 小程序退出时退出腾讯IM登录 - try { - if (globalTimChatManager && globalTimChatManager.tim) { - console.log('小程序退出,开始退出腾讯IM'); - globalTimChatManager.destroy(); - console.log('腾讯IM退出成功'); - } - } catch (error) { - console.error('退出腾讯IM失败:', error); - } + // try { + // if (globalTimChatManager && globalTimChatManager.tim) { + // console.log('小程序退出,开始退出腾讯IM'); + // globalTimChatManager.destroy(); + // console.log('腾讯IM退出成功'); + // } + // } catch (error) { + // console.error('退出腾讯IM失败:', error); + // } }, }; diff --git a/pages/message/chat.scss b/pages/message/chat.scss index 62ece8f..a885920 100644 --- a/pages/message/chat.scss +++ b/pages/message/chat.scss @@ -120,7 +120,6 @@ $primary-color: #0877F1; } .system-message { - // background-color: #f0f0f0; border-radius: 16rpx; padding: 12rpx; margin: 20rpx 24rpx; @@ -141,7 +140,7 @@ $primary-color: #0877F1; } .message-item { - margin-bottom: 16rpx; + margin-bottom: 30rpx; } .message-content { @@ -162,14 +161,14 @@ $primary-color: #0877F1; width: 60rpx; height: 60rpx; border-radius: 50%; - margin-top: 28rpx; // 向下移动与气泡箭头对齐 + margin-top: 10rpx; } .user-msg-avatar { width: 60rpx; height: 60rpx; border-radius: 50%; - margin-top: 28rpx; // 向下移动与气泡箭头对齐 + margin-top: 10rpx; } // 消息气泡容器 @@ -1040,6 +1039,19 @@ $primary-color: #0877F1; object-fit: cover; /* 保持图片比例,裁剪多余部分 */ } +/* 图片消息气泡 - 无背景色 */ +.image-bubble { + background: transparent !important; + padding: 0 !important; + border-radius: 0 !important; +} + +/* 移除图片消息气泡的小三角 */ +.image-bubble::before, +.image-bubble::after { + display: none !important; +} + .message-right .message-card { margin-right: 8rpx; } diff --git a/pages/message/components/chat-input.vue b/pages/message/components/chat-input.vue index f103256..571e398 100644 --- a/pages/message/components/chat-input.vue +++ b/pages/message/components/chat-input.vue @@ -146,8 +146,9 @@ const sendTextMessage = async () => { }; // 发送图片消息 -const sendImageMessage = async (imageUrl) => { - await sendMessage('image', imageUrl); +const sendImageMessage = async (imageFile) => { + console.log('chat-input sendImageMessage 被调用,参数:', imageFile); + await sendMessage('image', imageFile); }; // 发送语音消息 @@ -164,6 +165,7 @@ const sendMessage = async (messageType, data) => { () => validateBeforeSend(false, false, props.timChatManager), () => { showMorePanel.value = false; + // 发送成功后滚动到底部 emit('messageSent'); }, cloudCustomData.value @@ -197,7 +199,13 @@ const toggleMorePanel = () => { // 处理图片选择 const showImagePicker = () => { chooseImage( - (res) => sendImageMessage(res), + (res) => { + console.log('选择图片成功,返回数据:', res); + // 提取实际的文件对象 + const imageFile = res.tempFiles && res.tempFiles.length > 0 ? res.tempFiles[0] : res; + console.log('准备发送图片:', imageFile); + sendImageMessage(imageFile); + }, (err) => { console.error('选择图片失败:', err); if (!err.errMsg?.includes('permission') && !err.errMsg?.includes('auth') && !err.errMsg?.includes('拒绝') && !err.errMsg?.includes('未授权')) { @@ -213,7 +221,13 @@ const showImagePicker = () => { const takePhoto = () => { takePhotoUtil( - (res) => sendImageMessage(res), + (res) => { + console.log('拍照成功,返回数据:', res); + // 提取实际的文件对象 + const imageFile = res.tempFiles && res.tempFiles.length > 0 ? res.tempFiles[0] : res; + console.log('准备发送图片:', imageFile); + sendImageMessage(imageFile); + }, (err) => { console.error('拍照失败:', err); if (!err.errMsg?.includes('permission') && !err.errMsg?.includes('auth') && !err.errMsg?.includes('拒绝') && !err.errMsg?.includes('未授权')) { @@ -315,10 +329,10 @@ const sendSurveyMessage = async () => { const morePanelButtons = [ { text: '照片', icon: '/static/home/photo.png', action: showImagePicker }, { text: '拍摄', icon: '/static/home/video.png', action: takePhoto }, - // { text: '病情', icon: '/static/home/doctor.png', action: sendSymptomMessage }, - // { text: '处方', icon: '/static/home/doctor.png', action: sendPrescriptionMessage }, - // { text: '续方', icon: '/static/home/doctor.png', action: sendRefillMessage }, - // { text: '问卷', icon: '/static/home/doctor.png', action: sendSurveyMessage } + // { text: '病情', icon: '/static/home/avatar.svg', action: sendSymptomMessage }, + // { text: '处方', icon: '/static/home/avatar.svg', action: sendPrescriptionMessage }, + // { text: '续方', icon: '/static/home/avatar.svg', action: sendRefillMessage }, + // { text: '问卷', icon: '/static/home/avatar.svg', action: sendSurveyMessage } ]; function handleInputFocus() { diff --git a/pages/message/components/head-card.vue b/pages/message/components/head-card.vue index cc94bc4..568b64e 100644 --- a/pages/message/components/head-card.vue +++ b/pages/message/components/head-card.vue @@ -45,7 +45,7 @@ const props = defineProps({ }) const hasFilledDescription = computed(() => props.order && ('description' in props.order)); -const avatar = computed(() => props.doctorInfo?.avatar || '/static/home/doctor.png') +const avatar = computed(() => props.doctorInfo?.avatar || '/static/home/avatar.svg') function addSymptomDescription() { if (!hasFilledDescription.value) { diff --git a/pages/message/index.vue b/pages/message/index.vue index c84fce7..19d0432 100644 --- a/pages/message/index.vue +++ b/pages/message/index.vue @@ -50,19 +50,19 @@ - + - + @@ -113,8 +113,8 @@ v-if="!isEvaluationPopupOpen" :timChatManager="timChatManager" :formatTime="formatTime" - @scrollToBottom="scrollToBottom" - @messageSent="scrollToBottom" + @scrollToBottom="() => scrollToBottom(true)" + @messageSent="() => scrollToBottom(true)" /> @@ -170,7 +170,7 @@ const updateNavigationTitle = () => { const chatInfo = ref({ conversationID: "", userID: "", - avatar: "/static/home/doctor.png", + avatar: "/static/home/avatar.svg", }); // 评价弹窗状态 @@ -196,6 +196,11 @@ function isSystemMessage(message) { // 获取消息气泡样式类 function getBubbleClass(message) { + // 图片消息不需要气泡背景 + if (message.type === "TIMImageElem") { + return "image-bubble"; + } + if (message.type === "TIMCustomElem") { return message.flow === "out" ? "user-bubble" : "doctor-bubble-blue"; } @@ -279,10 +284,9 @@ const initTIMCallbacks = async () => { if (!existingMessage) { messageList.value.push(message); console.log("✓ 添加消息到列表,当前消息数量:", messageList.value.length); + // 立即滚动到底部,不使用延迟 nextTick(() => { - setTimeout(() => { - scrollToBottom(); - }, 100); + scrollToBottom(true); }); } }); @@ -473,13 +477,24 @@ const playVoice = (message) => { }; // 滚动到底部 -const scrollToBottom = () => { +const scrollToBottom = (immediate = false) => { if (messageList.value.length > 0) { const lastMessage = messageList.value[messageList.value.length - 1]; - scrollIntoView.value = ``; - setTimeout(() => { - scrollIntoView.value = `msg-${lastMessage.ID}`; - }, 300); + const targetId = `msg-${lastMessage.ID}`; + + if (immediate) { + // 立即滚动:先清空再设置,触发滚动 + scrollIntoView.value = ''; + nextTick(() => { + scrollIntoView.value = targetId; + }); + } else { + // 正常滚动,使用短延迟确保DOM更新 + scrollIntoView.value = ''; + setTimeout(() => { + scrollIntoView.value = targetId; + }, 50); + } } }; diff --git a/pages/message/message.vue b/pages/message/message.vue index 7e1d423..0cacc4d 100644 --- a/pages/message/message.vue +++ b/pages/message/message.vue @@ -122,20 +122,15 @@ const initIM = async () => { // 加载会话列表 const loadConversationList = async () => { if (loading.value) return; - - loading.value = true; + // loading.value = true; try { console.log("开始加载群聊列表"); - - // 检查 globalTimChatManager 是否存在 if (!globalTimChatManager || !globalTimChatManager.getGroupList) { throw new Error("IM管理器未初始化"); } - // 直接调用getGroupList,它会自动等待SDK就绪 const result = await globalTimChatManager.getGroupList(); - if (result && result.success && result.groupList) { conversationList.value = result.groupList .map((group) => ({ @@ -220,34 +215,72 @@ const extractMessagePreview = (message) => { return "暂无消息"; }; -// 设置消息监听,实时更新列表 -const setupMessageListener = () => { +// 设置会话列表监听,实时更新列表 +const setupConversationListener = () => { if (!globalTimChatManager) return; + // 监听会话列表更新事件 + globalTimChatManager.setCallback("onConversationListUpdated", (eventData) => { + console.log("会话列表更新事件:", eventData); + + // 如果是新消息导致的会话更新 + if (eventData.reason === "NEW_MESSAGE_RECEIVED_IN_CURRENT_CONVERSATION" || + eventData.reason === "NEW_MESSAGE_RECEIVED") { + const conversation = eventData.conversation; + if (!conversation) return; + + const conversationID = conversation.conversationID; + const conversationIndex = conversationList.value.findIndex( + (conv) => conv.conversationID === conversationID + ); + + if (conversationIndex !== -1) { + // 更新现有会话 + const existingConversation = conversationList.value[conversationIndex]; + existingConversation.lastMessage = conversation.lastMessage || "暂无消息"; + existingConversation.lastMessageTime = conversation.lastMessageTime || Date.now(); + existingConversation.unreadCount = conversation.unreadCount || 0; + + // 将该会话移到顶部 + const [updatedConversation] = conversationList.value.splice( + conversationIndex, + 1 + ); + conversationList.value.unshift(updatedConversation); + + console.log("已更新会话:", existingConversation.name); + } else { + // 新会话,添加到列表顶部 + conversationList.value.unshift({ + conversationID: conversationID, + groupID: conversation.groupID || conversationID.replace("GROUP", ""), + name: conversation.name || "问诊群聊", + avatar: conversation.avatar || "/static/default-avatar.png", + lastMessage: conversation.lastMessage || "暂无消息", + lastMessageTime: conversation.lastMessageTime || Date.now(), + unreadCount: conversation.unreadCount || 0, + }); + + console.log("已添加新会话"); + } + } + }); + + // 监听消息接收事件(用于更新未读数) globalTimChatManager.setCallback("onMessageReceived", (message) => { console.log("消息列表页面收到新消息:", message); - // 找到对应的会话并更新 + // 找到对应的会话并更新未读数 const conversationID = message.conversationID; const conversationIndex = conversationList.value.findIndex( (conv) => conv.conversationID === conversationID ); if (conversationIndex !== -1) { - // 更新现有会话 const conversation = conversationList.value[conversationIndex]; - conversation.lastMessage = extractMessagePreview(message); - conversation.lastMessageTime = message.lastTime || Date.now(); + // 只更新未读数,其他信息由 onConversationListUpdated 事件处理 conversation.unreadCount = (conversation.unreadCount || 0) + 1; - - // 将该会话移到顶部 - const [updatedConversation] = conversationList.value.splice( - conversationIndex, - 1 - ); - conversationList.value.unshift(updatedConversation); - - console.log("已更新会话:", conversation.name); + console.log("已更新会话未读数:", conversation.name); } }); }; @@ -341,11 +374,11 @@ onShow(async () => { return; } - // 先设置消息监听 - setupMessageListener(); - - // 加载消息列表 + // 先加载初始会话列表 await loadConversationList(); + + // 再设置监听器,后续通过事件更新列表 + setupConversationListener(); } catch (error) { console.error("页面初始化失败:", error); uni.showToast({ @@ -359,6 +392,7 @@ onShow(async () => { onHide(() => { // 移除消息监听 if (globalTimChatManager) { + globalTimChatManager.setCallback("onConversationListUpdated", null); globalTimChatManager.setCallback("onMessageReceived", null); } }); diff --git a/static/icon/changyongyu.png b/static/icon/changyongyu.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6de393c77f68bd2a781a5718272b3a253628a0 GIT binary patch literal 2793 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 LNkvXXu0mjfrVGGJ literal 0 HcmV?d00001 diff --git a/static/icon/icon-chinese-rx.png b/static/icon/icon-chinese-rx.png new file mode 100644 index 0000000000000000000000000000000000000000..10ee891d7137aa4c92c56830a6c7938e266b2f3d GIT binary patch literal 4406 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 literal 0 HcmV?d00001 diff --git a/static/icon/icon-western-rx.png b/static/icon/icon-western-rx.png new file mode 100644 index 0000000000000000000000000000000000000000..00cb9a8c93c7bb49e8a37b61163965203c92c31f GIT binary patch literal 3880 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 Eg2io%BLDyZ literal 0 HcmV?d00001 diff --git a/static/icon/kaiyizhu.png b/static/icon/kaiyizhu.png new file mode 100644 index 0000000000000000000000000000000000000000..e47bc0c927bc3c3b234f7a8eb14275a6753eca66 GIT binary patch literal 2637 zcmV-T3bOTyP)=>}TJ;eKl|pI1-!$egpmrZUqm6XTc7z8;nM9*=_E#k9{5ESbgYA zpS2+?Hy9cLAo5^v3ItsSZUO5f{Ezk`@N;lH_$K%YI2g=~;IiA?XCM1I#<6uj`n=2- z!2S#^-U<@{67~mk!P($GsMrPW0n5P2;A`L@kmObCSbgYApZYe&E@J{?o5TLZDa3LY zfTCxDlc;tr@YT7R*7eWO=4Hk*rm@XotvLaYsJwCrU_J$G2J67F;Im-Z%^c0uoHtto$V`Y#1R&ur!1Cqbdhi`ERn6S3;qs`a)ayu9 z0FvAfoJp2XfZu^Hf~juSU@cEr8`v++f>INJ#115}3&20Ym%(&1YqGWrqFPg_FR=h5 zGXr?2ZX~0>fzN~Cw)ojun>B8E)6(QZPUL2w0v*`b0Mzwt=L9-CLsWGZD{EpvPS(NG37~IY z`|e1fZvkhwzVESESqBE>MvmmF!|wJ?eF=b~PO`3MxS#wqm33i2j;@Y4>(Xs}S5EDatC|<$!6`UR5-Z(U zIlD08aMI!@6@YQ&3ql2INpKJ9_CTK&03z zMtCipCb{k0^=5F*wd;bjmM#y@_`|Zoux0C$!RY8%@`C#JAcw0WPTNLx6o6ho172dL zNyqNGdsFb}<6DEVv0RI9OMYZzZ}7}>&$n&Qc!ONZXG=`I4PsjaV5@g{d{BwSa#+qsg51h+gWQGyDC6^yOW5NB zN;KuLS}oTjL2j2sT!#cV1Yqr}z)x$M@>y0#LI)=0Sgyle%>w+8Enf$vv|25uH$A*o8H*Jz;pbvc| z^^?PRIX^-H{9;-T@w$h2Y|GYQ#b5rB_)eKV9>Z7nXhXAit^&*%nsmyS$Q%Xmy{LSp z4@bq}X{e8%3`aWU%VL&Czyal}duS>?Kmn>Fpg29kBcQnOO_80wI*Uy@6Ye$M7{+P} zUFf1mz(}DPM8TcCI*W}~Gi!`ttjq!o=6FN_-W_aA)jLH7-c^91I0Br#I*U!QxQ?!j zVXTfj^WPXA0ekX~+a58knQ*V?PKRu!yvHM8XFDBnwp3$cFtAerw#OJ)4M~TA?Fz61 zU;991`7$8qI~3pz;uiin;JLZy=)KP4lkPW$v8cIdlk+zeU^kfvn&QlJbI;MU&cGPP z$~sOT{^fkP0=z=!mqBIuG9c%#D1e7T?q3<1SgYseo}*9Pp7y#ijFqmyKEJ`BjUoR2@6h!SrT_d!ruJ`6k;;vj%Yd9- z8gbn4+#*1<^,L8>-$rr!Zi)n#VRGRjp><+dg7HUUuRReD?jQnh(UemlT-sy?%D zW{&2Xf$uBuEw?SM+XRUA-cIYMHBCD9lON9yzJKh|K}N5xRFX6|b2Qgf9DeFVF6H#o zEzwd(0Z_^t^~>q=CP>A+4E*~u76(_YS(WG;FiGzRbQ^DO=4h^|6g}=l4wpxqj&EuD z-BEyO|7O}dL1oDpkVCob=w(s?BE?oP>NPN2Hg&R*yA=_a9kZTP0DCj=En5F3@_V$E z4aR_+T^w;Z>Bz1EM5FxL_`jm%s_CBrIg`7tBYFyef*;~x4G6Da(^l4-0l8Weao4qX zPXQc_BX3*!-;thjy+T(8-gSA?-wI?buu>h#=*^!r97m&$C zz$rM*v@~n8#s$>e-U~`@nyHcq-e@0)8pb z2b5SGDl;c@GsjcO`38^~@%73x7oZ-9VqOK;vpx^Z2R8v9R7!j%fuFc$Y;!OdbDC$4 zpzY~}xB+^N%T0iKA{6JPjQd&lQieyxGL~KUnRym-F=H9i*yiA+g!|1cm*`DFau=W; zn_@@7c5o#)neDT|kHBhhA9xYG1AJ?siDiz}hraZwZ)40hCNQ=+_*`tP07(ZGCcwnR zA>(b})x`7I_Y2@FQ#j-FAL-x5wnxBoz%RJ@r4laz2$$XFKKt0$F^<)T(C2x^0QSdC vZ_B#JCjbBd|NljvzC!>200v1!K~w_(hvP?wp{zty00000NkvXXu0mjfBl!tj literal 0 HcmV?d00001 diff --git a/static/icon/kaizhongyao.png b/static/icon/kaizhongyao.png new file mode 100644 index 0000000000000000000000000000000000000000..f9d649e5f04635d8ceda22b06d4986650ab078aa GIT binary patch literal 1288 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 bKX?t$C;l{jzO+b}umVt5)l#WcvJCqV+_3%h literal 0 HcmV?d00001 diff --git a/static/icon/quxiaobingtuikuan.png b/static/icon/quxiaobingtuikuan.png new file mode 100644 index 0000000000000000000000000000000000000000..97c24a321ac59b9ac012d892bd73ff786e74d600 GIT binary patch literal 3930 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 a=>cZ4X{qjWfo&%<07E_GjZ$5gsQ&@{f?g~D literal 0 HcmV?d00001 diff --git a/static/icon/xuanjiaowenzhang.png b/static/icon/xuanjiaowenzhang.png new file mode 100644 index 0000000000000000000000000000000000000000..dffdb3734f15d82eddd1b83619eb4eaba2cc5e29 GIT binary patch literal 2708 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 literal 0 HcmV?d00001 diff --git a/static/icon/zhenliaoyijian.png b/static/icon/zhenliaoyijian.png new file mode 100644 index 0000000000000000000000000000000000000000..08851f4651b41b21b8125bce812553dab26a34dd GIT binary patch literal 3057 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 { const isIMInitialized = ref(false); // 医生信息 const doctorInfo = ref(null); - + async function login(phoneCode = '') { if (loading.value) return; loading.value = true; @@ -39,8 +39,8 @@ 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); @@ -51,7 +51,7 @@ export default defineStore("accountStore", () => { console.error('腾讯IM初始化失败:', imError); // IM初始化失败不影响登录流程 } - + await getDoctorInfo(openid.value); return res.data } } @@ -61,7 +61,7 @@ export default defineStore("accountStore", () => { } loading.value = false } - + async function getDoctorInfo() { try { const res = await api('getCorpMemberData', { @@ -88,7 +88,7 @@ export default defineStore("accountStore", () => { return false; } } - + // 退出登录 async function logout() { try { @@ -101,7 +101,7 @@ export default defineStore("accountStore", () => { } catch (error) { console.error('退出腾讯IM失败:', error); } - + // 清空账户信息 account.value = null; openid.value = ""; diff --git a/utils/chat-utils.js b/utils/chat-utils.js index 01164ee..f172069 100644 --- a/utils/chat-utils.js +++ b/utils/chat-utils.js @@ -486,6 +486,8 @@ export const sendCustomMessage = async (messageData, timChatManager, validateBef * @param {function} onSuccess - 成功回调 */ export const sendMessage = async (messageType, data, timChatManager, validateBeforeSend, onSuccess, cloudCustomData) => { + console.log('chat-utils sendMessage 被调用:', { messageType, data }); + if (!validateBeforeSend()) { return; } @@ -497,7 +499,9 @@ export const sendMessage = async (messageType, data, timChatManager, validateBef result = await timChatManager.sendTextMessage(data, cloudCustomData); break; case 'image': + console.log('准备发送图片消息,数据:', data); result = await timChatManager.sendImageMessage(data, cloudCustomData); + console.log('图片消息发送结果:', result); break; case 'voice': result = await timChatManager.sendVoiceMessage(data.file, data.duration,cloudCustomData); @@ -508,6 +512,7 @@ export const sendMessage = async (messageType, data, timChatManager, validateBef } if (result && result.success) { + console.log('消息发送成功'); if (onSuccess) onSuccess(); } else { console.error('发送消息失败:', result?.error); diff --git a/utils/tim-chat.js b/utils/tim-chat.js index 631aec1..d57d8ce 100644 --- a/utils/tim-chat.js +++ b/utils/tim-chat.js @@ -427,27 +427,18 @@ class TimChatManager { userID: this.currentUserID, userSig: this.currentUserSig }).then(() => { - console.log('腾讯IM登录成功') + console.log('腾讯IM登录请求成功,等待SDK_READY事件...') this.isLoggingIn = false - this.isLoggedIn = true + // 不在这里设置 isLoggedIn = true,等待 onSDKReady 事件 this.reconnectAttempts = 0 - // 启动心跳检测 - this.startHeartbeat() - - // 启动登录状态检测 - this.startLoginStatusCheck() - - // 触发登录状态变化回调 + // 触发登录状态变化回调(登录中状态) this.triggerCallback('onLoginStatusChanged', { - isLoggedIn: true, + isLoggedIn: false, userID: this.currentUserID, - reason: 'LOGIN_SUCCESS' + reason: 'LOGIN_REQUESTED', + message: '登录请求成功,等待SDK就绪...' }) - - // 获取会话列表(确保连接正常) - this.getConversationList() - resolve() }).catch(error => { console.error('腾讯IM登录失败:', error) @@ -691,7 +682,6 @@ class TimChatManager { }) this.startLoginStatusCheck() this.startHeartbeat() // 启动心跳检测 - this.getConversationList() } // SDK Not Ready 事件 @@ -712,11 +702,8 @@ class TimChatManager { 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) - // 确保使用消息本身的conversationID,而不是当前会话ID if (!convertedMessage.conversationID) { convertedMessage.conversationID = message.conversationID } @@ -731,30 +718,22 @@ class TimChatManager { messageType: convertedMessage.type, from: convertedMessage.from }) - console.log(event) - - // 缓存功能已移除 - // 判断是否为当前会话的消息(必须有currentConversationID且匹配才显示) const isCurrentConversation = this.currentConversationID && messageConversationID === this.currentConversationID - console.log('消息会话匹配检查:', { isCurrentConversation, hasCurrentConversationID: !!this.currentConversationID, conversationIDMatch: messageConversationID === this.currentConversationID }) - if (isCurrentConversation) { // 当前会话的消息,触发回调 console.log('✓ 消息属于当前会话,触发显示') this.triggerCallback('onMessageReceived', convertedMessage) - // 处理已读状态 if (this.currentConversationID) { this.markConversationAsRead(this.currentConversationID) } - this.triggerCallback('onConversationListUpdated', { reason: 'NEW_MESSAGE_RECEIVED_IN_CURRENT_CONVERSATION', conversation: { @@ -928,7 +907,6 @@ class TimChatManager { return true } - // 启动心跳检测(优化版:使用配置常量) startHeartbeat() { this.stopHeartbeat() @@ -952,14 +930,26 @@ class TimChatManager { return } + // 确保方法存在 + if (typeof this.tim.getConversationList !== 'function') { + console.log('⏸ 心跳检测:SDK方法不可用,跳过检测') + return + } + this.tim.getConversationList() .then(() => { if (this.heartbeatFailCount > 0) { - console.log(`💚 心跳恢复正常(之前失败${this.heartbeatFailCount}次)`) + console.log(`� 心跳恢复正常(之前失败${this.heartbeatFailCount}次)`) } this.heartbeatFailCount = 0 // 重置失败计数 }) .catch((error) => { + // 如果是SDK未就绪错误,不计入失败次数(这是临时状态) + if (error && error.message && error.message.includes('sdk not ready')) { + console.log('⏸ 心跳检测:SDK未就绪,跳过本次检测') + return + } + this.heartbeatFailCount++ console.error(`💔 心跳失败 (${this.heartbeatFailCount}/${MAX_HEARTBEAT_FAIL}):`, error.message) @@ -1009,14 +999,6 @@ class TimChatManager { setTimeout(checkSDKReady, 500) return } - - this.tim.getConversationList({ withGroupInfo: 1, withAllFields: 1 }).then(response => { - if (this.conversationID) { - this.enterConversation(this.conversationID) - } - }).catch(error => { - console.error('获取会话列表失败:', error) - }) } // 获取群聊列表 @@ -1032,18 +1014,21 @@ class TimChatManager { let waitTime = 0 const maxWaitTime = 30000 // 最多等待30秒 const checkInterval = 1000 // 每秒检查一次 + let timeoutHandle = null const checkSDKReady = () => { if (this.isLoggedIn) { console.log('SDK已ready,开始获取群聊列表') + if (timeoutHandle) clearTimeout(timeoutHandle) this.getGroupListInternal().then(resolve).catch(reject) } else if (waitTime >= maxWaitTime) { console.error('等待SDK就绪超时') + if (timeoutHandle) clearTimeout(timeoutHandle) reject(new Error('SDK初始化超时,请检查网络连接')) } else { waitTime += checkInterval console.log(`等待SDK就绪... (${Math.floor(waitTime / 1000)}/${Math.floor(maxWaitTime / 1000)}秒)`) - setTimeout(checkSDKReady, checkInterval) + timeoutHandle = setTimeout(checkSDKReady, checkInterval) } } @@ -1060,124 +1045,104 @@ class TimChatManager { return new Promise((resolve, reject) => { console.log('开始获取群聊列表') - // 确保SDK已就绪再调用getConversationList - const ensureSDKReady = () => { - if (!this.isLoggedIn) { - console.log('SDK未就绪,等待中...') - setTimeout(ensureSDKReady, 500) - return - } + // 直接调用,SDK就绪检查已在getGroupList()中完成 + this.tim.getConversationList() + .then(async (conversationResponse) => { + console.log('获取会话列表成功') + + const groupConversations = conversationResponse.data.conversationList.filter(conversation => { + return conversation.conversationID && conversation.conversationID.startsWith('GROUP') + }) - this.tim.getConversationList().then(async (conversationResponse) => { - console.clear() - console.log('获取会话列表成功:', conversationResponse) - const groupConversations = conversationResponse.data.conversationList.filter(conversation => { - return conversation.conversationID && conversation.conversationID.startsWith('GROUP') - }) + console.log('群聊会话列表数量:', groupConversations.length) - console.log('群聊会话列表:', groupConversations) - - const groupsWithInfo = await Promise.all( - groupConversations.map(async (conversation) => { - const groupName = typeof conversation.groupProfile.name === 'string' ? conversation.groupProfile.name : conversation.groupProfile.name; - const [doctorId, patientName] = groupName.split('|') - try { - const groupID = conversation.conversationID.replace('GROUP', '') - let groupInfo = { - groupID: groupID, - name: '问诊群聊', - avatar: '/static/home/doctor.png', - memberCount: 0 - } + // 先获取一次群组列表,避免在循环中重复调用 + let allGroups = [] + try { + const groupListResponse = await this.tim.getGroupList() + allGroups = groupListResponse.data.groupList || [] + } catch (error) { + console.error('获取群组列表失败:', error) + } + const groupsWithInfo = await Promise.all( + groupConversations.map(async (conversation) => { try { - const groupListResponse = await this.tim.getGroupList() - const group = groupListResponse.data.groupList.find(g => g.groupID === groupID) - if (group) { - groupInfo = { - ...groupInfo, - name: group.name || '问诊群聊', - memberCount: group.memberCount || 0 + const groupName = conversation.groupProfile?.name || '' + const [doctorId, patientName] = groupName.split('|') + const groupID = conversation.conversationID.replace('GROUP', '') + + // 从已获取的群组列表中查找 + const group = allGroups.find(g => g.groupID === groupID) + const groupInfo = { + groupID: groupID, + name: group?.name || '问诊群聊', + avatar: '/static/home/avatar.svg', + memberCount: group?.memberCount || 0 + } + + const lastMessage = conversation.lastMessage + let lastMessageText = '暂无消息' + let lastMessageTime = Date.now() + + if (lastMessage) { + if (lastMessage.type === 'TIMTextElem') { + lastMessageText = lastMessage.payload.text + } else if (lastMessage.type === 'TIMImageElem') { + lastMessageText = '[图片]' + } else if (lastMessage.type === 'TIMSoundElem') { + lastMessageText = '[语音]' + } else if (lastMessage.type === 'TIMCustomElem') { + lastMessageText = lastMessage.payload.data || '[自定义消息]' + } else { + lastMessageText = '[未知消息类型]' } + + lastMessageTime = (lastMessage.lastTime || lastMessage.time || 0) * 1000 + } + + return { + ...groupInfo, + groupID: groupID, + doctorId, + patientName, + conversationID: conversation.conversationID, + lastMessage: lastMessageText, + lastMessageTime: lastMessageTime, + unreadCount: conversation.unreadCount || 0 } } catch (error) { - console.error(`获取群组 ${groupID} 信息失败:`, error) - } - - const lastMessage = conversation.lastMessage - let lastMessageText = '' - let lastMessageTime = Date.now() - - if (lastMessage) { - console.log(`群聊 ${groupID} 最后一条消息:`, 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 ? lastMessage.payload.data : '[自定义消息]' - } else { - lastMessageText = '[未知消息类型]' + 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 } + } + }) + ) - if (lastMessage.lastTime) { - lastMessageTime = lastMessage.lastTime * 1000 - } else if (lastMessage.time) { - lastMessageTime = lastMessage.time * 1000 - } - } else { - console.log(`群聊 ${groupID} 没有最后一条消息`) - lastMessageText = '暂无消息' - } - - return { - ...groupInfo, - groupID: groupID, - doctorId, - patientName, - conversationID: conversation.conversationID, - lastMessage: lastMessageText, - lastMessageTime: lastMessageTime, - unreadCount: conversation.unreadCount || 0 - } - } catch (error) { - console.error(`处理群聊会话失败:`, error) - return { - groupID: conversation.conversationID, - name: conversation.groupProfile.name, - doctorId, - patientName, - avatar: '/static/home/avatar.svg', - lastMessage: '获取失败', - lastMessageTime: Date.now(), - unreadCount: conversation.unreadCount || 0, - memberCount: 0 - } - } + console.log('处理后的群聊列表数量:', groupsWithInfo.length) + resolve({ + success: true, + groupList: groupsWithInfo, + totalCount: groupsWithInfo.length, + data: conversationResponse.data }) - ) - - console.log('处理后的群聊列表:', groupsWithInfo) - resolve({ - success: true, - groupList: groupsWithInfo, - totalCount: groupsWithInfo.length, - data: conversationResponse.data }) - }).catch((imError) => { - console.error('获取会话列表失败:', imError) - reject({ - success: false, - error: imError + .catch((imError) => { + console.error('获取会话列表失败:', imError) + reject({ + success: false, + error: imError + }) }) - }) - } - - // 开始检查SDK就绪状态 - ensureSDKReady() }) } @@ -1564,7 +1529,7 @@ class TimChatManager { payload: this.convertDBPayloadToIMPayload(msgType, msgBody.MsgContent), lastTime: lastTime, status: 'success', - avatar: flow === 'in' ? '/static/home/doctor.png' : '/static/center/user-avatar.png', + avatar: flow === 'in' ? '/static/home/avatar.svg' : '/static/center/user-avatar.png', conversationID: this.currentConversationID, MsgSeq: dbMsg.MsgSeq, // 保留 MsgSeq 用于分页 } @@ -2074,29 +2039,42 @@ class TimChatManager { if (!this.tim) { this.triggerCallback('onError', 'IM未初始化') - return + return { success: false, error: 'IM未初始化' } } - if (!this.conversation) { - this.triggerCallback('onError', '群聊会话不存在') - return { success: false, error: '群聊会话不存在' } + // 检查登录状态 + if (!this.isLoggedIn) { + console.error('IM未登录,无法发送消息'); + this.triggerCallback('onError', 'IM未登录,请稍后重试') + return { success: false, error: 'IM未登录' } } - let groupID = null - if (this.conversation.groupProfile && this.conversation.groupProfile.groupID) { - groupID = this.conversation.groupProfile.groupID - } else if (this.conversation.conversationID) { - groupID = this.conversation.conversationID.replace('GROUP', '') + // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取 + let conversationID = this.currentConversationID; + if (!conversationID && this.conversation) { + conversationID = this.conversation.conversationID; + } + + if (!conversationID) { + console.error('会话ID不存在'); + this.triggerCallback('onError', '会话不存在,请重新进入聊天') + return { success: false, error: '会话ID不存在' } + } + + // 从 conversationID 提取 groupID + let groupID = null; + if (conversationID.startsWith('GROUP')) { + groupID = conversationID.replace('GROUP', ''); + } else if (this.conversation?.groupProfile?.groupID) { + groupID = this.conversation.groupProfile.groupID; } if (!groupID) { + console.error('无法获取群聊ID,conversationID:', conversationID); this.triggerCallback('onError', '无法获取群聊ID') return { success: false, error: '无法获取群聊ID' } } - // 确保使用当前会话的conversationID - const conversationID = this.conversation.conversationID || this.currentConversationID - const localMessage = { ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, flow: 'out', @@ -2127,39 +2105,75 @@ class TimChatManager { } catch (error) { console.error('文本消息发送失败:', error) localMessage.status = 'failed' + + // 如果是因为未登录导致的失败,尝试重连 + if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) { + console.log('检测到未登录错误,尝试重连...'); + this.isLoggedIn = false; + this.ensureIMConnection(); + } + return { success: false, error } } } // 发送图片消息 async sendImageMessage(imageFile) { + console.log('sendImageMessage 被调用,参数:', imageFile); + if (!this.tim) { this.triggerCallback('onError', 'IM未初始化') - return + return { success: false, error: 'IM未初始化' } } - if (!this.conversation) { - this.triggerCallback('onError', '群聊会话不存在') - return { success: false, error: '群聊会话不存在' } + // 检查登录状态 + if (!this.isLoggedIn) { + console.error('IM未登录,无法发送消息'); + this.triggerCallback('onError', 'IM未登录,请稍后重试') + return { success: false, error: 'IM未登录' } } - let groupID = null - if (this.conversation.groupProfile && this.conversation.groupProfile.groupID) { - groupID = this.conversation.groupProfile.groupID - } else if (this.conversation.conversationID) { - groupID = this.conversation.conversationID.replace('GROUP', '') + // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取 + let conversationID = this.currentConversationID; + if (!conversationID && this.conversation) { + conversationID = this.conversation.conversationID; + } + + if (!conversationID) { + console.error('会话ID不存在'); + this.triggerCallback('onError', '会话不存在,请重新进入聊天') + return { success: false, error: '会话ID不存在' } + } + + // 从 conversationID 提取 groupID + let groupID = null; + if (conversationID.startsWith('GROUP')) { + groupID = conversationID.replace('GROUP', ''); + } else if (this.conversation?.groupProfile?.groupID) { + groupID = this.conversation.groupProfile.groupID; } if (!groupID) { + console.error('无法获取群聊ID,conversationID:', conversationID); this.triggerCallback('onError', '无法获取群聊ID') return { success: false, error: '无法获取群聊ID' } } - // 确保使用当前会话的conversationID - const conversationID = this.conversation.conversationID || this.currentConversationID + console.log('发送图片消息,conversationID:', conversationID, 'groupID:', groupID); + + // 处理文件对象 - 确保获取正确的文件 + let actualFile = imageFile; + if (imageFile?.tempFiles?.length > 0) { + actualFile = imageFile.tempFiles[0]; + console.log('从 tempFiles 中提取文件:', actualFile); + } else if (imageFile?.tempFilePath) { + // 如果已经是单个文件对象 + actualFile = imageFile; + console.log('使用单个文件对象:', actualFile); + } // 获取图片尺寸信息 - const imageInfo = await this.getImageInfo(imageFile); + const imageInfo = await this.getImageInfo(actualFile); const localMessage = { ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, @@ -2167,7 +2181,7 @@ class TimChatManager { type: 'TIMImageElem', payload: { imageInfoArray: [{ - url: this.getImageUrl(imageFile), + url: this.getImageUrl(actualFile), width: imageInfo.width, height: imageInfo.height }] @@ -2186,19 +2200,33 @@ class TimChatManager { // 触发消息接收回调,让UI立即显示 this.triggerCallback('onMessageReceived', localMessage) + console.log('准备创建 TIM 图片消息,groupID:', groupID, 'file:', actualFile); + const message = this.tim.createImageMessage({ to: groupID, conversationType: TIM.TYPES.CONV_GROUP, - payload: { file: imageFile } + payload: { file: actualFile } }) + console.log('TIM 图片消息已创建:', message); + try { - await this.tim.sendMessage(message) + console.log('开始发送图片消息...'); + const sendResult = await this.tim.sendMessage(message); + console.log('图片消息发送成功:', sendResult); localMessage.status = 'success' return { success: true, message: localMessage } } catch (error) { console.error('图片消息发送失败:', error) localMessage.status = 'failed' + + // 如果是因为未登录导致的失败,尝试重连 + if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) { + console.log('检测到未登录错误,尝试重连...'); + this.isLoggedIn = false; + this.ensureIMConnection(); + } + return { success: false, error } } } @@ -2207,29 +2235,42 @@ class TimChatManager { async sendVoiceMessage(voiceFile, duration) { if (!this.tim) { this.triggerCallback('onError', 'IM未初始化') - return + return { success: false, error: 'IM未初始化' } } - if (!this.conversation) { - this.triggerCallback('onError', '群聊会话不存在') - return { success: false, error: '群聊会话不存在' } + // 检查登录状态 + if (!this.isLoggedIn) { + console.error('IM未登录,无法发送消息'); + this.triggerCallback('onError', 'IM未登录,请稍后重试') + return { success: false, error: 'IM未登录' } } - let groupID = null - if (this.conversation.groupProfile && this.conversation.groupProfile.groupID) { - groupID = this.conversation.groupProfile.groupID - } else if (this.conversation.conversationID) { - groupID = this.conversation.conversationID.replace('GROUP', '') + // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取 + let conversationID = this.currentConversationID; + if (!conversationID && this.conversation) { + conversationID = this.conversation.conversationID; + } + + if (!conversationID) { + console.error('会话ID不存在'); + this.triggerCallback('onError', '会话不存在,请重新进入聊天') + return { success: false, error: '会话ID不存在' } + } + + // 从 conversationID 提取 groupID + let groupID = null; + if (conversationID.startsWith('GROUP')) { + groupID = conversationID.replace('GROUP', ''); + } else if (this.conversation?.groupProfile?.groupID) { + groupID = this.conversation.groupProfile.groupID; } if (!groupID) { + console.error('无法获取群聊ID,conversationID:', conversationID); this.triggerCallback('onError', '无法获取群聊ID') return { success: false, error: '无法获取群聊ID' } } - // 确保使用当前会话的conversationID - const conversationID = this.conversation.conversationID || this.currentConversationID - const localMessage = { ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, flow: 'out', @@ -2262,6 +2303,14 @@ class TimChatManager { } catch (error) { console.error('语音消息发送失败:', error) localMessage.status = 'failed' + + // 如果是因为未登录导致的失败,尝试重连 + if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) { + console.log('检测到未登录错误,尝试重连...'); + this.isLoggedIn = false; + this.ensureIMConnection(); + } + return { success: false, error } } } @@ -2270,29 +2319,42 @@ class TimChatManager { async sendCustomMessage(messageData) { if (!this.tim) { this.triggerCallback('onError', 'IM未初始化') - return + return { success: false, error: 'IM未初始化' } } - if (!this.conversation) { - this.triggerCallback('onError', '群聊会话不存在') - return { success: false, error: '群聊会话不存在' } + // 检查登录状态 + if (!this.isLoggedIn) { + console.error('IM未登录,无法发送消息'); + this.triggerCallback('onError', 'IM未登录,请稍后重试') + return { success: false, error: 'IM未登录' } } - let groupID = null - if (this.conversation.groupProfile && this.conversation.groupProfile.groupID) { - groupID = this.conversation.groupProfile.groupID - } else if (this.conversation.conversationID) { - groupID = this.conversation.conversationID.replace('GROUP', '') + // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取 + let conversationID = this.currentConversationID; + if (!conversationID && this.conversation) { + conversationID = this.conversation.conversationID; + } + + if (!conversationID) { + console.error('会话ID不存在'); + this.triggerCallback('onError', '会话不存在,请重新进入聊天') + return { success: false, error: '会话ID不存在' } + } + + // 从 conversationID 提取 groupID + let groupID = null; + if (conversationID.startsWith('GROUP')) { + groupID = conversationID.replace('GROUP', ''); + } else if (this.conversation?.groupProfile?.groupID) { + groupID = this.conversation.groupProfile.groupID; } if (!groupID) { + console.error('无法获取群聊ID,conversationID:', conversationID); this.triggerCallback('onError', '无法获取群聊ID') return { success: false, error: '无法获取群聊ID' } } - // 确保使用当前会话的conversationID - const conversationID = this.conversation.conversationID || this.currentConversationID - const localMessage = { ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, flow: 'out', @@ -2330,6 +2392,14 @@ class TimChatManager { } catch (error) { console.error('自定义消息发送失败:', error) localMessage.status = 'failed' + + // 如果是因为未登录导致的失败,尝试重连 + if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) { + console.log('检测到未登录错误,尝试重连...'); + this.isLoggedIn = false; + this.ensureIMConnection(); + } + return { success: false, error } } } @@ -2364,7 +2434,7 @@ class TimChatManager { payload: timMessage.payload, lastTime: lastTime, status: timMessage.status || 'success', - avatar: timMessage.flow === 'in' ? '/static/home/doctor.png' : '/static/center/user-avatar.png', + avatar: timMessage.flow === 'in' ? '/static/home/avatar.svg' : '/static/center/user-avatar.png', // 优先使用消息本身的conversationID,确保消息归属正确的会话 conversationID: timMessage.conversationID } @@ -2408,12 +2478,19 @@ class TimChatManager { } getImageUrl(imageFile) { + // 处理 tempFiles 数组格式 if (imageFile?.tempFiles?.length > 0) { return imageFile.tempFiles[0].tempFilePath } + // 处理单个文件对象 + if (imageFile?.tempFilePath) { + return imageFile.tempFilePath + } + // 处理字符串路径 if (typeof imageFile === 'string') { return imageFile } + console.warn('无法获取图片URL,使用默认图片:', imageFile); return '/static/home/photo.png' } @@ -2422,17 +2499,22 @@ class TimChatManager { return new Promise((resolve) => { let imagePath = ''; - // 获取图片路径 + // 获取图片路径 - 处理多种格式 if (imageFile?.tempFiles?.length > 0) { imagePath = imageFile.tempFiles[0].tempFilePath; + } else if (imageFile?.tempFilePath) { + imagePath = imageFile.tempFilePath; } else if (typeof imageFile === 'string') { imagePath = imageFile; } else { + console.warn('无法获取图片路径,使用默认尺寸:', imageFile); // 默认尺寸 resolve({ width: 400, height: 300 }); return; } + console.log('获取图片信息,路径:', imagePath); + // 使用uni.getImageInfo获取图片尺寸 uni.getImageInfo({ src: imagePath,