miniprogramme/pages/message/message.js
2025-09-12 16:08:17 +08:00

1399 lines
No EOL
42 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

// 消息页面逻辑 - 基于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);
});
}
});