// 消息页面逻辑 - 基于Flutter实现 const app = getApp(); const apiClient = require('../../utils/api-client.js'); const wsManager = require('../../utils/websocket-manager-v2.js'); const chatAPI = require('../../utils/chat-api.js'); // 移除未使用的 friendAPI 依赖,避免无意义的打包与阅读负担 const { initPageSystemInfo } = require('../../utils/system-info-modern.js'); Page({ data: { searchKeyword: '', refreshing: false, loading: false, // 会话数据 conversations: [], pinnedConversations: [], normalConversations: [], totalUnreadCount: 0, // 过滤后的数据 filteredConversations: [], // WebSocket状态 wsConnected: false, // 系统适配信息 systemInfo: {}, statusBarHeight: 0, menuButtonHeight: 0, menuButtonTop: 0, navBarHeight: 0, windowHeight: 0, safeAreaBottom: 0, showSearch: false, // 新增:控制搜索栏的显示/隐藏 themeClass: 'theme-dark', isThemeTransitioning: false, // 主题切换径向遮罩 showThemeOverlay: false, overlayPlaying: false, overlayTo: 'theme-light', overlayX: 0, overlayY: 0, overlayDiameter: 0, overlayLeft: 0, overlayTop: 0, // 本地模糊搜索增强:抽样最近消息文本 searching: false, conversationSamples: {}, // { [conversationId]: string[] } sampleLoading: {}, // { [conversationId]: boolean } lastSearchKeyword: '' }, // 统一时间戳规范化:支持 number/纯数字字符串/ISO 字符串 normalizeTimestamp(ts) { if (!ts && ts !== 0) return 0; if (typeof ts === 'number' && isFinite(ts)) return ts; if (typeof ts === 'string') { const s = ts.trim(); if (!s) return 0; // 纯数字(可能是秒/毫秒) if (/^\d+$/.test(s)) { const n = parseInt(s, 10); // 假定 10 位为秒,13 位为毫秒 return s.length === 10 ? n * 1000 : n; } // ISO 或其他可被 Date 解析的格式 const d = Date.parse(s); return isNaN(d) ? 0 : d; } // 其他类型直接尝试 Date try { const d = new Date(ts).getTime(); return isNaN(d) ? 0 : d; } catch (_) { return 0; } }, // 页面加载 onLoad: function (options) { console.log('消息页面加载'); this.initSystemInfo(); this.checkAuthAndInit(); // 🔥 动态设置导航栏标题 wx.setNavigationBarTitle({ title: '消息' }); }, // 检查认证状态并初始化 async checkAuthAndInit() { try { console.log('🔍 开始检查用户认证状态...'); // 检查多种token来源 const userInfo = wx.getStorageSync('userInfo'); const directToken = wx.getStorageSync('token'); const apiToken = apiClient.getToken(); console.log('🔍 Token检查结果:', { hasUserInfo: !!userInfo, hasUserInfoToken: !!(userInfo?.token), hasDirectToken: !!directToken, hasApiToken: !!apiToken, userInfoTokenLength: userInfo?.token?.length || 0, directTokenLength: directToken?.length || 0, apiTokenLength: apiToken?.length || 0 }); // 确保API客户端能获取到token const currentToken = apiToken || userInfo?.token || directToken; if (!currentToken) { console.error('❌ 用户未登录,准备跳转到登录页'); this.redirectToLogin('checkAuthAndInit'); return; } console.log('✅ 消息页面认证检查通过,开始初始化'); // 确保API客户端有token if (!apiToken && currentToken) { apiClient.setToken(currentToken); console.log('🔑 已设置API客户端token'); } // 初始化WebSocket和加载数据 this.initWebSocket(); this.loadConversations(); } catch (error) { console.error('❌ 消息页面认证检查失败:', error); this.redirectToLogin('exception'); } }, // 🔥 统一的登录页跳转方法 - 使用退出登录的成功模式 redirectToLogin(source) { // 防止重复跳转 if (this.redirecting) { console.log(`⚠️ 正在跳转中,忽略来自${source}的跳转请求`); return; } this.redirecting = true; console.log(`🔄 来自${source}的跳转请求,执行跳转...`); // 🔥 使用退出登录的成功模式:固定1000ms延迟 + 简单的reLaunch setTimeout(() => { wx.reLaunch({ url: '/pages/login/login', success: () => { console.log('✅ 跳转到登录页成功'); this.redirecting = false; }, fail: (error) => { console.error('❌ 跳转到登录页失败:', error); this.redirecting = false; } }); }, 1000); // 🔥 使用与退出登录相同的1000ms延迟 }, // 初始化系统信息 initSystemInfo() { const pageSystemInfo = initPageSystemInfo(); console.log('消息页面系统适配信息:', { statusBarHeight: pageSystemInfo.statusBarHeight, menuButtonHeight: pageSystemInfo.menuButtonHeight, menuButtonTop: pageSystemInfo.menuButtonTop, navBarHeight: pageSystemInfo.navBarHeight, windowHeight: pageSystemInfo.windowHeight, safeAreaBottom: pageSystemInfo.safeAreaBottom }); this.setData({ systemInfo: pageSystemInfo.systemInfo, statusBarHeight: pageSystemInfo.statusBarHeight, menuButtonHeight: pageSystemInfo.menuButtonHeight, menuButtonTop: pageSystemInfo.menuButtonTop, navBarHeight: pageSystemInfo.navBarHeight, windowHeight: pageSystemInfo.windowHeight, safeAreaBottom: pageSystemInfo.safeAreaBottom }); }, // 初始化WebSocket连接 initWebSocket() { try { console.log('初始化WebSocket连接'); // 获取用户token - 使用更可靠的方式 let userInfo = app.globalData.userInfo; let token = null; if (userInfo && userInfo.token) { token = userInfo.token; } else { // 尝试从本地存储获取 try { const storedUserInfo = wx.getStorageSync('userInfo'); if (storedUserInfo && storedUserInfo.token) { token = storedUserInfo.token; userInfo = storedUserInfo; // 更新全局数据 app.globalData.userInfo = userInfo; app.globalData.isLoggedIn = true; } } catch (error) { console.error('从本地存储获取用户信息失败:', error); } } if (!token) { console.error('WebSocket连接失败: 缺少认证token'); // 不直接return,而是设置一个延迟重试 setTimeout(() => { this.initWebSocket(); }, 2000); return; } // 设置token并连接 wsManager.setToken(token); // 注册消息处理器 wsManager.on('new_message', (message) => { console.log('收到新消息:', message); this.handleNewMessage(message.data); }); wsManager.on('conversation_update', (message) => { console.log('会话更新:', message); this.handleConversationUpdate(message.data); }); wsManager.on('friend_request', (message) => { console.log('收到好友请求:', message); this.handleFriendRequest(message.data); }); wsManager.on('global_connected', () => { console.log('WebSocket连接成功'); this.setData({ wsConnected: true }); }); wsManager.on('global_disconnected', () => { console.log('WebSocket连接断开'); this.setData({ wsConnected: false }); }); // 🔥 注册聊天功能处理器 wsManager.onChatMessage((message) => { console.log('🔥 收到新聊天消息:', message); this.handleNewMessage(message.data); }); wsManager.onUnreadCountUpdate((message) => { console.log('🔥 未读数更新:', message); this.updateUnreadCount(message.data); }); // 🔥 注册好友功能处理器 wsManager.onNotification((message) => { console.log('🔥 收到通知:', message); this.handleNotification(message.data); }); wsManager.onFriendRequest((message) => { console.log('🔥 收到好友请求:', message); this.handleFriendRequest(message.data); }); // 🔥 注册在线状态事件(presence) const handleOnline = (msg) => { console.log('👤 用户上线:', msg); this.updatePresenceStatus(msg?.data || msg, true); }; const handleOffline = (msg) => { console.log('👤 用户离线:', msg); this.updatePresenceStatus(msg?.data || msg, false); }; const handlePresence = (msg) => { const data = msg?.data || msg; const online = typeof data?.online === 'boolean' ? data.online : undefined; console.log('👤 在线状态更新:', data); if (online !== undefined) { this.updatePresenceStatus(data, online); } }; this._presenceHandlers = { handleOnline, handleOffline, handlePresence }; wsManager.on('user_online', handleOnline); wsManager.on('user_offline', handleOffline); wsManager.on('presence_update', handlePresence); // 开始连接 wsManager.connect(); } catch (error) { console.error('初始化WebSocket失败:', error); } }, // 处理新消息 handleNewMessage(messageData) { console.log('处理新消息:', messageData); // 保护:如果没有会话ID或消息为空,直接返回 if (!messageData) return; // 复制列表以防直接修改原数组 const conversations = Array.isArray(this.data.conversations) ? [...this.data.conversations] : []; // 更健壮的匹配:支持 conversationId、id、targetId(或 senderId)等字段 const incomingConvId = messageData.conversationId || messageData.conversation_id || messageData.id || ''; const incomingTargetId = messageData.targetId || messageData.receiverId || messageData.to || messageData.senderId || ''; let foundIndex = conversations.findIndex(conv => { return conv.conversationId === incomingConvId || conv.id === incomingConvId || conv.targetId === incomingTargetId || conv.targetId === (messageData.senderId || messageData.from); }); if (foundIndex >= 0) { // 更新现有会话 const conv = conversations[foundIndex]; const ts = Number(messageData.sendTime || Date.now()); conversations[foundIndex] = { ...conv, lastMessage: this.getMessagePreview(messageData), lastMessageType: messageData.msgType || 'text', lastMessageTime: this.formatMessageTime(ts), lastMsgTime: ts, // 只有当消息不是自己发出的情况下才增加未读 unreadCount: (conv.unreadCount || 0) + ((messageData.senderId && (messageData.senderId !== (this.data.userInfo?.user?.customId || ''))) ? 1 : 0) }; console.log('更新会话(index=' + foundIndex + ')未读数为:', conversations[foundIndex].unreadCount); } else { // 未找到会话:创建一个最小化的会话记录并插入到列表顶部 const newConvId = incomingConvId || `conv_${Date.now()}`; const senderName = messageData.senderName || messageData.fromName || '陌生人'; const ts = Number(messageData.sendTime || Date.now()); const newConversation = { id: newConvId, conversationId: newConvId, type: messageData.chatType || 0, name: senderName, avatar: messageData.senderAvatar || '', isOnline: false, isPinned: false, isMuted: false, unreadCount: 1, lastMessage: this.getMessagePreview(messageData), lastMessageType: messageData.msgType || 'text', lastMessageTime: this.formatMessageTime(ts), lastMsgTime: ts, messageStatus: 'received', targetId: incomingTargetId || messageData.senderId || '' }; conversations.unshift(newConversation); console.log('新增会话以显示未读提示:', newConversation); } // 写回并统一处理排序/角标等 this.setData({ conversations }); this.processConversations(conversations); // 播放提示音 this.playMessageSound(); // 更新tab角标(processConversations 内也会调用,但这里保证即时性) this.updateTabBadge(); }, // 获取消息预览 getMessagePreview(message) { switch (message.msgType) { case 'text': return message.content || '[文本消息]'; case 'image': return '[图片]'; case 'audio': return '[语音]'; case 'video': return '[视频]'; case 'file': return '[文件]'; case 'location': return '[位置]'; default: return '[消息]'; } }, // 处理会话更新 handleConversationUpdate(updateData) { console.log('会话更新:', updateData); // 重新加载会话列表 this.loadConversations(); }, // 处理好友请求(精简:保留一个实现) handleFriendRequest(requestData) { console.log('收到好友请求:', requestData); wx.showToast({ title: '收到新的好友请求', icon: 'none', duration: 2000 }); }, // 页面显示 onShow: function () { console.log('消息页面显示'); // 初始化主题 const savedTheme = wx.getStorageSync('theme') || 'theme-dark'; this.setData({ themeClass: savedTheme }); // 🔥 在刷新前检查登录状态 const currentToken = apiClient.getToken(); if (!currentToken) { console.warn('⚠️ onShow检测到用户未登录,跳转到登录页'); this.redirectToLogin('onShow'); return; } this.refreshConversations(); // 启动定时刷新 if (this._refreshTimer) clearInterval(this._refreshTimer); this._refreshTimer = setInterval(() => { console.log('定时刷新会话列表...'); this.loadConversations(); }, 10000); // 每10秒刷新一次 }, // 页面隐藏 onHide: function () { console.log('消息页面隐藏'); if (this._refreshTimer) { clearInterval(this._refreshTimer); this._refreshTimer = null; } }, // 页面卸载 onUnload: function () { console.log('消息页面卸载'); if (this._refreshTimer) { clearInterval(this._refreshTimer); this._refreshTimer = null; } // 移除WebSocket监听器 wsManager.off('new_message'); wsManager.off('conversation_update'); wsManager.off('friend_request'); wsManager.off('global_connected'); wsManager.off('global_disconnected'); if (this._presenceHandlers) { wsManager.off('user_online', this._presenceHandlers.handleOnline); wsManager.off('user_offline', this._presenceHandlers.handleOffline); wsManager.off('presence_update', this._presenceHandlers.handlePresence); this._presenceHandlers = null; } }, // 🔥 在线状态更新:根据 userId/targetId 更新对应会话的 isOnline updatePresenceStatus(data, isOnline) { try { const userId = data?.userId || data?.targetId || data?.uid || data?.id || data?.customId; if (!userId) { console.warn('⚠️ presence 事件缺少用户ID:', data); return; } const conversations = (this.data.conversations || []).map(conv => { // 单聊:targetId 等于该用户;群聊忽略 if ((conv.type === 'single' || conv.chatType === 0) && (conv.targetId == userId || String(conv.targetId) === String(userId))) { return { ...conv, isOnline: !!isOnline }; } return conv; }); // 仅当有变化时写回 const changed = JSON.stringify(conversations) !== JSON.stringify(this.data.conversations || []); if (changed) { this.setData({ conversations }); this.processConversations(conversations); } } catch (err) { console.error('❌ 更新在线状态失败:', err); } }, // 加载会话列表 async loadConversations() { if (this.data.loading) return; // 🔥 在加载前检查登录状态 const currentToken = apiClient.getToken(); if (!currentToken) { console.warn('⚠️ loadConversations检测到用户未登录,停止加载'); return; } try { this.setData({ loading: true }); console.log('🔥 开始加载会话列表'); const response = await chatAPI.getConversations(); console.log('会话列表API响应:', response); if (response && response.code === 0) { const conversations = response.data || []; console.log('处理会话数据:', conversations); // 🔥 处理会话数据格式,过滤掉无效会话 const processedConversations = conversations .map(conv => this.processConversationData(conv)) .filter(conv => conv !== null); // 过滤掉null值 console.log('处理后的会话数量:', processedConversations.length); this.processConversations(processedConversations); } else { throw new Error(response?.message || '获取会话列表失败'); } } catch (error) { console.error('加载会话失败:', error); wx.showToast({ title: '加载失败', icon: 'none' }); } finally { this.setData({ loading: false }); } }, // 处理会话数据格式 processConversationData(conv) { // 🔥 完全使用后端返回的会话ID,不再前端生成 const conversationId = conv.id || conv.conversationId; if (!conversationId) { console.error('会话缺少ID,跳过该会话:', conv); return null; // 如果没有会话ID,跳过该会话 } console.log('使用后端会话ID:', conversationId); // 统一提取原始时间戳 const candidates = [conv.lastMsgTime, conv.lastMessageTimeTs, conv.updatedAt, conv.lastActiveAt]; let rawTs = 0; for (const c of candidates) { const v = this.normalizeTimestamp(c); if (v) { rawTs = v; break; } } return { id: conversationId, conversationId: conversationId, type: conv.type === 1 ? 'group' : 'single', // 使用正确的type字段 name: conv.targetName || conv.name || '未知', avatar: conv.targetAvatar || '', isOnline: conv.isOnline || false, isPinned: conv.isTop || false, isMuted: conv.isMuted || false, unreadCount: conv.unreadCount || 0, lastMessage: conv.lastMsgContent || conv.lastMessage || '', lastMessageType: conv.lastMsgType || 'text', lastMessageTime: this.formatMessageTime(rawTs), lastMsgTime: rawTs, messageStatus: conv.messageStatus || 'sent', targetId: conv.targetId || conv.userId || '', chatType: conv.type || conv.chatType || 0 // 使用正确的type字段 }; }, // 格式化消息时间(Telegram 风格) formatMessageTime(timestamp) { if (!timestamp) return ''; const now = new Date(); const msg = new Date(timestamp); // 今天:HH:mm if (now.toDateString() === msg.toDateString()) { const hh = msg.getHours().toString().padStart(2, '0'); const mm = msg.getMinutes().toString().padStart(2, '0'); return `${hh}:${mm}`; } // 昨天 const y = new Date(now); y.setDate(y.getDate() - 1); if (y.toDateString() === msg.toDateString()) { return '昨天'; } // 更早:YYYY/MM/DD const year = msg.getFullYear(); const month = (msg.getMonth() + 1).toString().padStart(2, '0'); const day = msg.getDate().toString().padStart(2, '0'); return `${year}/${month}/${day}`; }, // 播放消息提示音 playMessageSound() { try { // 简单的提示,小程序没有播放背景音频的能力 wx.vibrateShort(); } catch (error) { console.log('提示异常:', error); } }, // 更新Tab角标 updateTabBadge() { const totalUnread = this.data.totalUnreadCount; if (totalUnread > 0) { wx.setTabBarBadge({ index: 1, // 消息tab的索引 text: totalUnread > 99 ? '99+' : totalUnread.toString() }); } else { wx.removeTabBarBadge({ index: 1 }); } }, // 处理会话数据 processConversations(conversations) { // 过滤已删除项,并统一关键字段 const validList = (conversations || []) .filter(it => it && !it.deleted && !it._deleted) .map(it => ({ ...it, // 统一置顶字段(isTop 与 isPinned 兼容) isPinned: !!(it.isPinned || it.isTop), // 统一时间字段用于排序(降序) _ts: this.normalizeTimestamp(it.lastMsgTime || it.lastMessageTimeTs || it.lastActiveAt || it.updatedAt) })); // 置顶/非置顶分组并组内按时间降序 const pinnedConversations = validList .filter(item => item.isPinned) .sort((a, b) => b._ts - a._ts); const normalConversations = validList .filter(item => !item.isPinned) .sort((a, b) => b._ts - a._ts); const sortedAll = [...pinnedConversations, ...normalConversations]; const totalUnreadCount = validList.reduce((sum, item) => sum + (item.unreadCount || 0), 0); this.setData({ conversations: sortedAll, pinnedConversations, normalConversations, totalUnreadCount, filteredConversations: sortedAll }); // 更新角标 this.updateTabBadge(); }, // 刷新会话列表 async refreshConversations() { this.setData({ refreshing: true }); await this.loadConversations(); this.setData({ refreshing: false }); }, // 下拉刷新 onRefresh() { this.refreshConversations(); }, // 搜索输入 onSearchInput(e) { const keyword = (e.detail.value || '').trim(); this.setData({ searchKeyword: keyword, lastSearchKeyword: keyword, searching: !!keyword }); if (!keyword) { this.setData({ searching: false }); this.processConversations(this.data.conversations); return; } // 立即本地模糊过滤 this.fuzzyFilterConversations(keyword); // 异步预取若干可能相关会话的最近消息样本 this.prefetchTopConversationSamples(keyword); }, // 清除搜索 clearSearch() { this.setData({ searchKeyword: '', lastSearchKeyword: '', searching: false }); this.processConversations(this.data.conversations); }, // 显示/隐藏搜索栏 showSearchBar() { this.setData({ showSearch: true }); }, hideSearchBar() { this.setData({ showSearch: false, searchKeyword: '', lastSearchKeyword: '', searching: false }); this.processConversations(this.data.conversations); }, // 本地模糊过滤:名称/最后一条消息/抽样最近消息(文本) fuzzyFilterConversations(keyword) { if (!keyword) { this.processConversations(this.data.conversations); return; } const q = (keyword || '').toLowerCase(); const subseqScore = (text, query) => { if (!text || !query) return 0; const s = String(text).toLowerCase(); let pos = 0, score = 0, streak = 0; for (const ch of query) { const idx = s.indexOf(ch, pos); if (idx === -1) return score; score += 1; streak = (idx === pos) ? streak + 1 : 1; score += Math.min(streak, 3) * 0.5; pos = idx + 1; } if (s.startsWith(query)) score += 2; if (s.includes(query)) score += 1; return score; }; const samples = this.data.conversationSamples || {}; const list = this.data.conversations || []; const withScore = list.map(item => { const nameScore = subseqScore(item.name || '', q); const lastScore = subseqScore(item.lastMessage || '', q); const sampleTexts = samples[item.conversationId] || []; let sampleScore = 0; for (const t of sampleTexts) sampleScore = Math.max(sampleScore, subseqScore(t, q)); const score = nameScore * 2 + Math.max(sampleScore * 1.5, lastScore); return { item, score }; }).filter(x => x.score > 0); withScore.sort((a, b) => { if (b.score !== a.score) return b.score - a.score; const at = this.normalizeTimestamp(a.item.lastMsgTime || a.item.lastMessageTimeTs); const bt = this.normalizeTimestamp(b.item.lastMsgTime || b.item.lastMessageTimeTs); return bt - at; }); let filtered = withScore.map(x => x.item); if (filtered.length === 0) { // 兜底:简单包含匹配 filtered = list.filter(item => { const name = String(item.name || '').toLowerCase(); const msg = String(item.lastMessage || '').toLowerCase(); return name.includes(q) || msg.includes(q); }); } const pinnedFiltered = filtered.filter(i => i.isPinned); const normalFiltered = filtered.filter(i => !i.isPinned); this.setData({ pinnedConversations: pinnedFiltered, normalConversations: normalFiltered, filteredConversations: filtered }); }, // 预取:对可能相关的会话抓取最近消息文本样本 async prefetchTopConversationSamples(keyword) { const q = (keyword || '').toLowerCase(); const list = this.data.conversations || []; // 粗筛:名称/最后消息包含命中的,取前8 const rough = list .map(item => ({ item, s: (String(item.name||'').toLowerCase().includes(q)?2:0) + (String(item.lastMessage||'').toLowerCase().includes(q)?1:0) })) .sort((a,b)=>b.s-a.s) .slice(0,8) .map(x=>x.item); const tasks = []; for (const conv of rough) { const cid = conv.conversationId; if (!cid) continue; if ((this.data.conversationSamples||{})[cid]) continue; if ((this.data.sampleLoading||{})[cid]) continue; tasks.push(this.fetchConversationSample(conv)); if (tasks.length >= 4) break; // 控制并发与数量 } if (tasks.length) { await Promise.allSettled(tasks); // 关键词未变化时刷新一次 if ((this.data.lastSearchKeyword||'') === (keyword||'')) { this.fuzzyFilterConversations(keyword); } } }, async fetchConversationSample(conv) { const cid = conv.conversationId; try { const loading = { ...(this.data.sampleLoading||{}) }; loading[cid] = true; this.setData({ sampleLoading: loading }); const params = { receiverId: conv.targetId, chatType: conv.chatType !== undefined ? conv.chatType : (conv.type === 'group' ? 1 : 0), limit: 20, direction: 'before' }; const resp = await apiClient.get('/api/v1/chat/history', params); let texts = []; if (resp && (resp.code === 0 || resp.code === 200)) { const arr = Array.isArray(resp.data) ? resp.data : (Array.isArray(resp.data?.list) ? resp.data.list : []); texts = arr .map(m => { const c = (typeof m?.content === 'string' && m.content) ? m.content : (typeof m?.text === 'string' ? m.text : (typeof m?.summary === 'string' ? m.summary : '')); return c; }) .filter(s => typeof s === 'string' && s.trim()) .slice(0, 20); } const samples = { ...(this.data.conversationSamples||{}) }; samples[cid] = texts; const loadingDone = { ...(this.data.sampleLoading||{}) }; delete loadingDone[cid]; this.setData({ conversationSamples: samples, sampleLoading: loadingDone }); } catch (e) { const loadingDone = { ...(this.data.sampleLoading||{}) }; delete loadingDone[cid]; this.setData({ sampleLoading: loadingDone }); } }, // 移除旧版 filterConversations;已被 fuzzyFilterConversations 取代 // 打开聊天 openChat(e) { const conversation = e.currentTarget.dataset.conversation; console.log('打开聊天:', conversation); // 检查必要参数 if (!conversation.targetId) { console.error('缺少targetId,无法打开聊天'); wx.showToast({ title: '聊天信息错误', icon: 'none' }); return; } // 标记为已读 if (conversation.unreadCount > 0) { this.markConversationRead(conversation.conversationId); } // 🔥 确保chatType有默认值 const chatType = conversation.chatType !== undefined ? conversation.chatType : 0; console.log('确定的chatType:', chatType, '原始值:', conversation.chatType); // 跳转到聊天页面 const params = { conversationId: conversation.conversationId, targetId: conversation.targetId, name: encodeURIComponent(conversation.name), chatType: chatType }; // 若当前存在搜索关键词,则作为锚点关键词传递,用于在聊天页内定位 const kw = (this.data.searchKeyword || '').trim(); if (kw) { params.keyword = encodeURIComponent(kw); } const queryString = Object.keys(params) .map(key => `${key}=${params[key]}`) .join('&'); console.log('聊天页面参数:', params); wx.navigateTo({ url: `/pages/message/chat/chat?${queryString}` }); }, // 显示会话选项 showConversationOptions(e) { const conversation = e.currentTarget.dataset.conversation; const itemList = [ (conversation.isPinned || conversation.isTop) ? '取消置顶' : '置顶会话', conversation.isMuted ? '取消免打扰' : '免打扰', '删除会话' ]; wx.showActionSheet({ itemList: itemList, success: (res) => { switch (res.tapIndex) { case 0: this.toggleConversationTop(conversation.conversationId); break; case 1: this.toggleConversationMute(conversation.conversationId); break; case 2: this.deleteConversation(conversation.conversationId); break; } } }); }, // 标记会话已读 async markConversationRead(conversationId) { try { // TODO: 调用API标记已读 await apiClient.markMessagesRead(conversationId); // 更新本地数据 const conversations = this.data.conversations.map(conv => { if (conv.conversationId === conversationId) { return { ...conv, unreadCount: 0 }; } return conv; }); this.processConversations(conversations); } catch (error) { console.error('标记已读失败:', error); } }, // 全部标记为已读 markAllRead() { if (this._markingAll) return; const unreadIds = (this.data.conversations || []) .filter(c => (c.unreadCount || 0) > 0) .map(c => c.conversationId) .filter(Boolean); if (!unreadIds.length) { wx.showToast({ title: '暂无未读', icon: 'none' }); return; } wx.showModal({ title: '确认操作', content: '将所有会话标记为已读?', success: async (res) => { if (!res.confirm) return; this._markingAll = true; try { // 1) 立即本地清零,给用户即时反馈 const conversations = (this.data.conversations || []).map(conv => ({ ...conv, unreadCount: 0 })); this.processConversations(conversations); wx.showToast({ title: '全部已读', icon: 'success' }); // 2) 后台逐会话标记已读(并发执行) await Promise.allSettled(unreadIds.map(id => chatAPI.markAllRead(id))); // 3) 刷新总未读徽章(防止后端有残留) try { const resp = await chatAPI.getTotalUnreadCount(); const total = (resp && (resp.code === 0 || resp.code === 200) && (resp.data?.total || resp.total)) ? (resp.data?.total || resp.total) : 0; this.setData({ totalUnreadCount: total }); if (total > 0) { wx.setTabBarBadge({ index: 1, text: total > 99 ? '99+' : String(total) }); } else { wx.removeTabBarBadge({ index: 1 }); } } catch (_) { /* 忽略统计刷新失败 */ } } catch (error) { console.error('全部标记已读失败:', error); wx.showToast({ title: '操作失败', icon: 'none' }); } finally { this._markingAll = false; } } }); }, // 发起聊天入口:统一打开顶部菜单(创建群聊/添加好友/扫一扫/设置) startNewChat() { this.showMenu(); }, // 🔥 ===== 新增的聊天功能处理方法 ===== // 更新未读数 updateUnreadCount(data) { try { if (data.conversationId) { // 更新特定会话的未读数 const conversations = this.data.conversations.map(conv => { if (conv.conversationId === data.conversationId) { return { ...conv, unreadCount: data.unreadCount || 0 }; } return conv; }); this.setData({ conversations }); this.processConversations(conversations); } if (data.totalUnreadCount !== undefined) { // 更新总未读数 this.setData({ totalUnreadCount: data.totalUnreadCount }); // 更新tabBar徽章 if (data.totalUnreadCount > 0) { wx.setTabBarBadge({ index: 1, // 消息tab的索引 text: data.totalUnreadCount > 99 ? '99+' : data.totalUnreadCount.toString() }); } else { wx.removeTabBarBadge({ index: 1 }); } } } catch (error) { console.error('更新未读数失败:', error); } }, // 处理通知 handleNotification(data) { try { console.log('处理通知:', data); // 根据通知类型进行不同处理 switch (data.type) { case 'new_friend_request': this.handleFriendRequestNotification(data); break; case 'friend_accepted': this.handleFriendAcceptedNotification(data); break; case 'friend_rejected': this.handleFriendRejectedNotification(data); break; default: console.log('未知通知类型:', data.type); } } catch (error) { console.error('处理通知失败:', error); } }, // 移除重复的 handleFriendRequest 定义(上方已保留) // 处理好友请求通知 handleFriendRequestNotification(data) { wx.showToast({ title: `${data.senderName} 想和您成为好友`, icon: 'none', duration: 2000 }); }, // 处理好友请求被接受通知 handleFriendAcceptedNotification(data) { wx.showToast({ title: `${data.senderName} 接受了您的好友请求`, icon: 'success', duration: 2000 }); }, // 处理好友请求被拒绝通知 handleFriendRejectedNotification(data) { wx.showToast({ title: '好友请求被拒绝', icon: 'none', duration: 2000 }); }, // 🔥 ===== 会话操作增强 ===== // 置顶/取消置顶会话 async toggleConversationTop(conversationId) { try { const conversation = this.data.conversations.find(conv => conv.conversationId === conversationId); if (!conversation) return; const newTopStatus = !conversation.isTop; await chatAPI.updateConversation(conversationId, { isTop: newTopStatus }); // 更新本地数据 const conversations = this.data.conversations.map(conv => { if (conv.conversationId === conversationId) { return { ...conv, isTop: newTopStatus }; } return conv; }); this.setData({ conversations }); this.processConversations(conversations); wx.showToast({ title: newTopStatus ? '已置顶' : '已取消置顶', icon: 'success' }); } catch (error) { console.error('切换置顶状态失败:', error); wx.showToast({ title: '操作失败', icon: 'none' }); } }, // 静音/取消静音会话 async toggleConversationMute(conversationId) { try { const conversation = this.data.conversations.find(conv => conv.conversationId === conversationId); if (!conversation) return; const newMuteStatus = !conversation.isMuted; await chatAPI.updateConversation(conversationId, { isMuted: newMuteStatus }); // 更新本地数据 const conversations = this.data.conversations.map(conv => { if (conv.conversationId === conversationId) { return { ...conv, isMuted: newMuteStatus }; } return conv; }); this.setData({ conversations }); this.processConversations(conversations); wx.showToast({ title: newMuteStatus ? '已静音' : '已取消静音', icon: 'success' }); } catch (error) { console.error('切换静音状态失败:', error); wx.showToast({ title: '操作失败', icon: 'none' }); } }, // 删除会话 async deleteConversation(conversationId) { try { await chatAPI.deleteConversation(conversationId); // 从本地数据中移除 const conversations = this.data.conversations.filter(conv => conv.conversationId !== conversationId); this.setData({ conversations }); this.processConversations(conversations); wx.showToast({ title: '已删除', icon: 'success' }); } catch (error) { console.error('删除会话失败:', error); wx.showToast({ title: '删除失败', icon: 'none' }); } }, // 显示菜单 showMenu() { console.log('📋 显示菜单'); const itemList = ['创建群聊', '添加好友', '扫一扫', '设置']; wx.showActionSheet({ itemList: itemList, success: (res) => { switch (res.tapIndex) { case 0: this.createGroup(); break; case 1: this.addFriend(); break; case 2: this.scanQRCode(); break; case 3: this.openSettings(); break; default: console.log('选择菜单项:', res.tapIndex); } } }); }, // 创建群聊 createGroup() { console.log('👥 创建群聊'); wx.navigateTo({ url: '/pages/group/create-group/create-group' }); }, // 添加好友 addFriend() { console.log('👤 添加好友'); wx.navigateTo({ url: '/pages/social/search/search' }); }, // 扫一扫 scanQRCode() { console.log('📷 扫一扫'); wx.scanCode({ success: (res) => { console.log('扫码结果:', res); // 处理扫码结果 this.handleScanResult(res); }, fail: (error) => { console.error('❌ 扫码失败:', error); wx.showToast({ title: '扫码失败', icon: 'none' }); } }); }, // 处理扫码结果 handleScanResult(result) { const { result: scanResult } = result; try { // 尝试解析为JSON const data = JSON.parse(scanResult); if (data.type === 'user') { // 用户二维码 wx.navigateTo({ url: `/pages/social/user-profile/user-profile?userId=${data.userId}` }); } else if (data.type === 'group') { // 群聊二维码 wx.showModal({ title: '加入群聊', content: `确定要加入群聊"${data.groupName}"吗?`, success: (res) => { if (res.confirm) { this.joinGroupByQR(data.groupId); } } }); } else { // 其他类型 wx.showToast({ title: '不支持的二维码类型', icon: 'none' }); } } catch (error) { // 不是JSON格式,可能是URL或其他格式 if (scanResult.startsWith('http')) { // 网址 wx.showModal({ title: '打开网址', content: scanResult, success: (res) => { if (res.confirm) { // 复制到剪贴板 wx.setClipboardData({ data: scanResult }); } } }); } else { // 其他文本 wx.showModal({ title: '扫码结果', content: scanResult, showCancel: false }); } } }, // 通过二维码加入群聊 async joinGroupByQR(groupId) { try { wx.showLoading({ title: '加入群聊中...' }); const response = await apiClient.request({ url: `/api/v1/groups/${groupId}/join`, method: 'POST' }); wx.hideLoading(); if (response.success) { wx.showToast({ title: '加入群聊成功', icon: 'success' }); // 刷新会话列表 this.loadConversations(); } else { wx.showToast({ title: response.error || '加入群聊失败', icon: 'none' }); } } catch (error) { wx.hideLoading(); console.error('❌ 加入群聊失败:', error); wx.showToast({ title: '加入群聊失败', icon: 'none' }); } }, // 打开设置 openSettings() { console.log('⚙️ 打开设置'); wx.navigateTo({ url: '/pages/profile/profile' }); }, // 切换主题(带过渡动画) toggleTheme() { const next = this.data.themeClass === 'theme-dark' ? 'theme-light' : 'theme-dark'; const query = wx.createSelectorQuery(); query.select('.theme-toggle').boundingClientRect(); query.selectViewport().boundingClientRect(); query.exec((res) => { const btnRect = res && res[0]; const vpRect = res && res[1]; // 兜底:左上角区域,和按钮在左侧的位置一致 let cx = 24; let cy = (this.data.statusBarHeight || 0) + 16; if (btnRect) { cx = btnRect.left + btnRect.width / 2; cy = btnRect.top + btnRect.height / 2; } const w = vpRect ? vpRect.width : (this.data.systemInfo?.windowWidth || 375); const h = vpRect ? vpRect.height : (this.data.systemInfo?.windowHeight || this.data.windowHeight || 667); const dx = Math.max(cx, w - cx); const dy = Math.max(cy, h - cy); const radius = Math.ceil(Math.sqrt(dx * dx + dy * dy)) + 12; const diameter = radius * 2; const left = Math.round(cx - radius); const top = Math.round(cy - radius); this.setData({ isThemeTransitioning: true, showThemeOverlay: true, overlayPlaying: false, overlayTo: next, overlayX: cx, overlayY: cy, overlayDiameter: diameter, overlayLeft: left, overlayTop: top }); setTimeout(() => { this.setData({ overlayPlaying: true, themeClass: next }); try { wx.setStorageSync('theme', next); } catch (e) { console.warn('保存主题失败', e); } }, 16); setTimeout(() => { this.setData({ showThemeOverlay: false, overlayPlaying: false, isThemeTransitioning: false }); }, 420); }); } });