1291 lines
36 KiB
JavaScript
1291 lines
36 KiB
JavaScript
|
|
// 消息页面逻辑 - 完全基于 NIM SDK
|
|||
|
|
const app = getApp();
|
|||
|
|
const apiClient = require('../../utils/api-client.js');
|
|||
|
|
const nimConversationManager = require('../../utils/nim-conversation-manager.js');
|
|||
|
|
const nimPresenceManager = require('../../utils/nim-presence-manager.js');
|
|||
|
|
const { initPageSystemInfo } = require('../../utils/system-info-modern.js');
|
|||
|
|
|
|||
|
|
Page({
|
|||
|
|
data: {
|
|||
|
|
//模拟数据
|
|||
|
|
store:[{
|
|||
|
|
avatar:'../../images/group/testImg.jpg',
|
|||
|
|
nickname:'Jianying Liu',
|
|||
|
|
text:'Heyyy',
|
|||
|
|
tiem:'16:30'
|
|||
|
|
}],
|
|||
|
|
searchKeyword: '',
|
|||
|
|
refreshing: false,
|
|||
|
|
loading: false,
|
|||
|
|
longPressing: false, // 长按标记,防止长按后触发单击
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 会话数据
|
|||
|
|
conversations: [],
|
|||
|
|
pinnedConversations: [],
|
|||
|
|
normalConversations: [],
|
|||
|
|
totalUnreadCount: 0,
|
|||
|
|
filteredConversations: [],
|
|||
|
|
|
|||
|
|
// NIM SDK 状态
|
|||
|
|
nimReady: false,
|
|||
|
|
|
|||
|
|
// 系统适配信息
|
|||
|
|
systemInfo: {},
|
|||
|
|
statusBarHeight: 0,
|
|||
|
|
menuButtonHeight: 0,
|
|||
|
|
menuButtonTop: 0,
|
|||
|
|
navBarHeight: 0,
|
|||
|
|
windowHeight: 0,
|
|||
|
|
safeAreaBottom: 0,
|
|||
|
|
showSearch: false,
|
|||
|
|
|
|||
|
|
// 本地模糊搜索增强
|
|||
|
|
searching: false,
|
|||
|
|
conversationSamples: {},
|
|||
|
|
sampleLoading: {},
|
|||
|
|
lastSearchKeyword: ''
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 统一时间戳规范化
|
|||
|
|
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);
|
|||
|
|
return s.length === 10 ? n * 1000 : n;
|
|||
|
|
}
|
|||
|
|
const d = Date.parse(s);
|
|||
|
|
return isNaN(d) ? 0 : d;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const d = new Date(ts).getTime();
|
|||
|
|
return isNaN(d) ? 0 : d;
|
|||
|
|
} catch (_) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从会话数据中提取真实的用户ID
|
|||
|
|
* @param {Object} conv - 会话对象
|
|||
|
|
* @returns {string} - 用户ID
|
|||
|
|
*/
|
|||
|
|
extractUserIdFromConversation(conv) {
|
|||
|
|
let userId = conv.targetId || conv.userId || '';
|
|||
|
|
|
|||
|
|
// 如果 targetId 是 conversationId 格式(包含|符号),则解析
|
|||
|
|
if (userId && typeof userId === 'string' && userId.includes('|')) {
|
|||
|
|
const parts = userId.split('|');
|
|||
|
|
if (parts.length === 3) {
|
|||
|
|
userId = parts[2]; // 提取目标用户ID
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果还是没有,尝试从 conversationId 解析
|
|||
|
|
if (!userId && conv.conversationId && typeof conv.conversationId === 'string') {
|
|||
|
|
const parts = conv.conversationId.split('|');
|
|||
|
|
if (parts.length === 3) {
|
|||
|
|
userId = parts[2];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return userId || '';
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 页面加载
|
|||
|
|
onLoad: function (options) {
|
|||
|
|
const { store } = this.data;
|
|||
|
|
// 生成10次重复的数组(fill+flat 一行实现)
|
|||
|
|
const repeatedArr = Array(20).fill(store).flat();
|
|||
|
|
// 响应式更新:必须用 setData,页面才会渲染新数组
|
|||
|
|
this.setData({ repeatedArr });
|
|||
|
|
|
|||
|
|
|
|||
|
|
this.initSystemInfo();
|
|||
|
|
this.checkAuthAndInit();
|
|||
|
|
|
|||
|
|
wx.setNavigationBarTitle({ title: '消息' });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 检查认证状态并初始化
|
|||
|
|
async checkAuthAndInit() {
|
|||
|
|
try {
|
|||
|
|
const currentToken = apiClient.getToken();
|
|||
|
|
const app = getApp();
|
|||
|
|
const isLoggedIn = app?.globalData?.isLoggedIn || false;
|
|||
|
|
|
|||
|
|
console.log('检查认证状态:', { hasToken: !!currentToken, isLoggedIn });
|
|||
|
|
|
|||
|
|
if (!currentToken || !isLoggedIn) {
|
|||
|
|
console.warn('⚠️ 用户未登录,跳转到登录页');
|
|||
|
|
this.redirectToLogin('checkAuthAndInit');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化 NIM 模式
|
|||
|
|
await this.initNIMMode();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('认证检查失败:', error);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: `NIM初始化失败:${error}`,
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 统一的登录页跳转方法
|
|||
|
|
redirectToLogin(source) {
|
|||
|
|
if (this.redirecting) {
|
|||
|
|
console.log('已在跳转中,跳过');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.redirecting = true;
|
|||
|
|
console.log(`🔄 来自${source}的跳转请求,执行跳转...`);
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
wx.reLaunch({
|
|||
|
|
url: '/pages/login/login',
|
|||
|
|
success: () => {
|
|||
|
|
console.log('✅ 跳转登录页成功');
|
|||
|
|
this.redirecting = false;
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('❌ 跳转登录页失败:', err);
|
|||
|
|
this.redirecting = false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}, 1000);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 初始化系统信息
|
|||
|
|
initSystemInfo() {
|
|||
|
|
const pageSystemInfo = initPageSystemInfo();
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
systemInfo: pageSystemInfo.systemInfo,
|
|||
|
|
statusBarHeight: pageSystemInfo.statusBarHeight,
|
|||
|
|
menuButtonHeight: pageSystemInfo.menuButtonHeight,
|
|||
|
|
menuButtonTop: pageSystemInfo.menuButtonTop,
|
|||
|
|
navBarHeight: pageSystemInfo.navBarHeight,
|
|||
|
|
windowHeight: pageSystemInfo.windowHeight,
|
|||
|
|
safeAreaBottom: pageSystemInfo.safeAreaBottom
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 初始化 NIM 模式
|
|||
|
|
initNIMMode() {
|
|||
|
|
try {
|
|||
|
|
console.log('📡 初始化 NIM 模式');
|
|||
|
|
|
|||
|
|
const app = getApp();
|
|||
|
|
if (!app.globalData.nim) {
|
|||
|
|
console.error('❌ NIM 实例未初始化');
|
|||
|
|
wx.showToast({
|
|||
|
|
title: 'NIM未初始化',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 注册 NIM 事件监听器
|
|||
|
|
nimConversationManager.on('conversation_created', this.handleNIMConversationCreated.bind(this));
|
|||
|
|
nimConversationManager.on('conversation_deleted', this.handleNIMConversationDeleted.bind(this));
|
|||
|
|
nimConversationManager.on('conversation_changed', this.handleNIMConversationChanged.bind(this));
|
|||
|
|
nimConversationManager.on('new_message', this.handleNIMNewMessages.bind(this));
|
|||
|
|
nimConversationManager.on('message_revoked', this.handleNIMMessageRevoked.bind(this));
|
|||
|
|
|
|||
|
|
// 🔥 注册在线状态变化监听
|
|||
|
|
nimPresenceManager.on('presence_changed', this.handlePresenceChanged.bind(this));
|
|||
|
|
|
|||
|
|
this.setData({ nimReady: true });
|
|||
|
|
|
|||
|
|
// 加载会话列表
|
|||
|
|
this.loadConversations();
|
|||
|
|
|
|||
|
|
console.log('✅ NIM 模式初始化完成');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 初始化 NIM 模式失败:', error);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '初始化 NIM 模式失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 🔥 检查并重连 NIM(页面级别)
|
|||
|
|
async checkAndReconnectNim() {
|
|||
|
|
try {
|
|||
|
|
const app = getApp();
|
|||
|
|
|
|||
|
|
// 如果 NIM 未就绪,尝试初始化
|
|||
|
|
if (!this.data.nimReady) {
|
|||
|
|
console.log('⚠️ 页面 NIM 未就绪,尝试初始化');
|
|||
|
|
await this.checkAuthAndInit();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 触发全局 NIM 重连检查
|
|||
|
|
if (app && typeof app.checkAndReconnectNim === 'function') {
|
|||
|
|
await app.checkAndReconnectNim();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新初始化会话管理器(确保事件监听器有效)
|
|||
|
|
if (app.globalData.nim) {
|
|||
|
|
const nim = app.globalData.nim;
|
|||
|
|
|
|||
|
|
// 检查会话管理器是否已初始化
|
|||
|
|
if (!nimConversationManager.getInitStatus()) {
|
|||
|
|
console.log('🔄 重新初始化会话管理器');
|
|||
|
|
nimConversationManager.init(nim);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查在线状态管理器是否已初始化
|
|||
|
|
if (!nimPresenceManager.getInitStatus()) {
|
|||
|
|
console.log('🔄 重新初始化在线状态管理器');
|
|||
|
|
nimPresenceManager.init(nim);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 页面级 NIM 重连检查失败:', error);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// ===== NIM 事件处理方法 =====
|
|||
|
|
|
|||
|
|
// 处理 NIM 会话新增
|
|||
|
|
handleNIMConversationCreated(conversation) {
|
|||
|
|
console.log('📝 处理NIM会话新增:', conversation);
|
|||
|
|
const conversations = [conversation, ...this.data.conversations];
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
this.playMessageSound();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理 NIM 会话删除
|
|||
|
|
handleNIMConversationDeleted(conversationIds) {
|
|||
|
|
console.log('🗑️ 处理NIM会话删除:', conversationIds);
|
|||
|
|
const conversations = this.data.conversations.filter(
|
|||
|
|
conv => !conversationIds.includes(conv.conversationId)
|
|||
|
|
);
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理 NIM 会话更新
|
|||
|
|
handleNIMConversationChanged(changedConversations) {
|
|||
|
|
console.log('🔄 处理NIM会话更新,数量:', changedConversations?.length || 0);
|
|||
|
|
|
|||
|
|
if (!Array.isArray(changedConversations) || changedConversations.length === 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔥 增量更新:只更新变化的会话,而不是替换整个列表
|
|||
|
|
let conversations = [...this.data.conversations];
|
|||
|
|
let hasChanges = false;
|
|||
|
|
|
|||
|
|
changedConversations.forEach(changedConv => {
|
|||
|
|
const index = conversations.findIndex(
|
|||
|
|
conv => conv.conversationId === changedConv.conversationId
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (index >= 0) {
|
|||
|
|
// 检查是否真的有变化(避免不必要的更新)
|
|||
|
|
const oldConv = conversations[index];
|
|||
|
|
const hasRealChange =
|
|||
|
|
oldConv.unreadCount !== changedConv.unreadCount ||
|
|||
|
|
oldConv.lastMessage !== changedConv.lastMessage ||
|
|||
|
|
oldConv.isPinned !== changedConv.isPinned ||
|
|||
|
|
oldConv.isMuted !== changedConv.isMuted;
|
|||
|
|
|
|||
|
|
if (hasRealChange) {
|
|||
|
|
// 🔥 特殊处理:如果旧会话有未读数,新会话未读数为0,但最后消息没变
|
|||
|
|
// 说明这可能是标记已读导致的更新,不应该覆盖可能正在到来的新消息未读数
|
|||
|
|
if (oldConv.unreadCount > 0 && changedConv.unreadCount === 0 &&
|
|||
|
|
oldConv.lastMessage === changedConv.lastMessage) {
|
|||
|
|
console.log('⚠️ 检测到可能的标记已读事件,保留当前未读数');
|
|||
|
|
changedConv.unreadCount = oldConv.unreadCount;
|
|||
|
|
}
|
|||
|
|
conversations[index] = changedConv;
|
|||
|
|
hasChanges = true;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 新会话,添加到列表
|
|||
|
|
conversations.unshift(changedConv);
|
|||
|
|
hasChanges = true;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 只有真正有变化时才更新 UI
|
|||
|
|
if (hasChanges) {
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
} else {
|
|||
|
|
console.log('⚠️ 会话数据无实质变化,跳过更新');
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理 NIM 新消息
|
|||
|
|
async handleNIMNewMessages(messages) {
|
|||
|
|
console.log('📨 处理NIM新消息:', messages);
|
|||
|
|
|
|||
|
|
if (!Array.isArray(messages)) return;
|
|||
|
|
|
|||
|
|
let conversations = [...this.data.conversations];
|
|||
|
|
let hasChanges = false;
|
|||
|
|
let needReloadConversations = false;
|
|||
|
|
|
|||
|
|
messages.forEach(message => {
|
|||
|
|
const conversationId = message.conversationId;
|
|||
|
|
if (!conversationId) return;
|
|||
|
|
|
|||
|
|
const index = conversations.findIndex(
|
|||
|
|
conv => conv.conversationId === conversationId
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (index >= 0) {
|
|||
|
|
// 更新现有会话
|
|||
|
|
// 🔥 使用消息的 createTime,如果没有则保持原时间
|
|||
|
|
const messageTime = message.createTime || conversations[index].lastMsgTime || 0;
|
|||
|
|
conversations[index] = {
|
|||
|
|
...conversations[index],
|
|||
|
|
lastMessage: nimConversationManager.getMessagePreview(message),
|
|||
|
|
lastMessageType: nimConversationManager.getMessageType(message),
|
|||
|
|
lastMessageTime: nimConversationManager.formatMessageTime(messageTime),
|
|||
|
|
lastMsgTime: messageTime,
|
|||
|
|
unreadCount: (conversations[index].unreadCount || 0) + 1
|
|||
|
|
};
|
|||
|
|
hasChanges = true;
|
|||
|
|
} else {
|
|||
|
|
// 新会话,需要重新加载
|
|||
|
|
console.log('⚠️ 收到新会话消息,需要重新加载会话列表');
|
|||
|
|
needReloadConversations = true;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (hasChanges) {
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
this.playMessageSound();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (needReloadConversations) {
|
|||
|
|
this.loadConversations();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理 NIM 消息撤回
|
|||
|
|
handleNIMMessageRevoked(revokeNotifications) {
|
|||
|
|
console.log('↩️ 处理NIM消息撤回:', revokeNotifications);
|
|||
|
|
|
|||
|
|
if (!Array.isArray(revokeNotifications)) return;
|
|||
|
|
|
|||
|
|
let conversations = [...this.data.conversations];
|
|||
|
|
let hasChanges = false;
|
|||
|
|
|
|||
|
|
revokeNotifications.forEach(notification => {
|
|||
|
|
const conversationId = notification.conversationId;
|
|||
|
|
if (!conversationId) return;
|
|||
|
|
|
|||
|
|
const index = conversations.findIndex(
|
|||
|
|
conv => conv.conversationId === conversationId
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (index >= 0) {
|
|||
|
|
conversations[index] = {
|
|||
|
|
...conversations[index],
|
|||
|
|
lastMessage: '已撤回'
|
|||
|
|
};
|
|||
|
|
hasChanges = true;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (hasChanges) {
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 🔥 处理用户在线状态变化
|
|||
|
|
handlePresenceChanged(data) {
|
|||
|
|
const { userId, isOnline } = data;
|
|||
|
|
|
|||
|
|
// 更新会话列表中对应用户的在线状态
|
|||
|
|
let conversations = [...this.data.conversations];
|
|||
|
|
let hasChanges = false;
|
|||
|
|
|
|||
|
|
conversations.forEach(conv => {
|
|||
|
|
// 判断是否为单聊会话
|
|||
|
|
const isSingleChat = conv.type === 'single' || conv.type === 0 || conv.chatType === 0;
|
|||
|
|
|
|||
|
|
if (!isSingleChat) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提取真实的用户ID进行匹配
|
|||
|
|
let targetId = conv.targetId;
|
|||
|
|
|
|||
|
|
// 如果 targetId 包含 | 符号,说明是 conversationId 格式,需要解析
|
|||
|
|
if (targetId && typeof targetId === 'string' && targetId.includes('|')) {
|
|||
|
|
const parts = targetId.split('|');
|
|||
|
|
if (parts.length === 3) {
|
|||
|
|
targetId = parts[2];
|
|||
|
|
conv.targetId = targetId; // 更新为纯用户ID
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (targetId === userId) {
|
|||
|
|
conv.isOnline = isOnline;
|
|||
|
|
hasChanges = true;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (hasChanges) {
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 加载会话列表
|
|||
|
|
async loadConversations() {
|
|||
|
|
if (this.data.loading) return;
|
|||
|
|
|
|||
|
|
const currentToken = apiClient.getToken();
|
|||
|
|
if (!currentToken) {
|
|||
|
|
console.warn('⚠️ Token 丢失,无法加载会话列表');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
this.setData({ loading: true });
|
|||
|
|
|
|||
|
|
console.log('📋 从 NIM SDK 加载会话列表');
|
|||
|
|
const conversations = await nimConversationManager.getConversationList(0, 100);
|
|||
|
|
|
|||
|
|
console.log('✅ 加载会话列表成功,数量:', conversations.length);
|
|||
|
|
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 加载会话列表失败:', error);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: `加载会话列表失败:${error}`,
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
} finally {
|
|||
|
|
this.setData({ loading: false });
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 播放消息提示音
|
|||
|
|
playMessageSound() {
|
|||
|
|
try {
|
|||
|
|
const innerAudioContext = wx.createInnerAudioContext();
|
|||
|
|
innerAudioContext.src = '/images/message-sound.mp3';
|
|||
|
|
innerAudioContext.play();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('播放提示音失败:', error);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 更新Tab角标
|
|||
|
|
updateTabBadge() {
|
|||
|
|
const totalUnread = this.data.totalUnreadCount;
|
|||
|
|
if (totalUnread > 0) {
|
|||
|
|
wx.setTabBarBadge({
|
|||
|
|
index: 1,
|
|||
|
|
text: totalUnread > 99 ? '99+' : totalUnread.toString()
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
wx.removeTabBarBadge({ index: 1 });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 自定义TabBar同步
|
|||
|
|
try {
|
|||
|
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
|||
|
|
this.getTabBar().setMessagesBadge(totalUnread);
|
|||
|
|
}
|
|||
|
|
} catch (_) { }
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理会话数据
|
|||
|
|
async processConversations(conversations) {
|
|||
|
|
// 过滤已删除项,并统一关键字段
|
|||
|
|
const validList = (conversations || [])
|
|||
|
|
.filter(it => it && !it.deleted && !it._deleted)
|
|||
|
|
.map(it => ({
|
|||
|
|
...it,
|
|||
|
|
isPinned: !!(it.isPinned || it.isTop),
|
|||
|
|
_ts: this.normalizeTimestamp(it.lastMsgTime || it.lastMessageTimeTs || it.lastActiveAt || it.updatedAt)
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
// 🔥 订阅会话中单聊用户的在线状态
|
|||
|
|
try {
|
|||
|
|
await nimPresenceManager.subscribeConversations(validList);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('订阅在线状态失败:', error);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔥 从缓存中更新在线状态
|
|||
|
|
validList.forEach(conv => {
|
|||
|
|
// 判断是否为单聊
|
|||
|
|
const isSingleChat = conv.type === 'single' || conv.type === 0 || conv.chatType === 0;
|
|||
|
|
|
|||
|
|
if (isSingleChat) {
|
|||
|
|
// 提取真实的用户ID(处理可能的 conversationId 格式)
|
|||
|
|
let targetId = conv.targetId;
|
|||
|
|
|
|||
|
|
// 如果 targetId 包含 | 符号,说明是 conversationId 格式,需要解析
|
|||
|
|
if (targetId && typeof targetId === 'string' && targetId.includes('|')) {
|
|||
|
|
const parts = targetId.split('|');
|
|||
|
|
if (parts.length === 3) {
|
|||
|
|
targetId = parts[2];
|
|||
|
|
conv.targetId = targetId; // 更新为纯用户ID
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!targetId) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const presence = nimPresenceManager.getUserPresence(targetId);
|
|||
|
|
|
|||
|
|
if (presence) {
|
|||
|
|
conv.isOnline = presence.online;
|
|||
|
|
} else {
|
|||
|
|
conv.isOnline = false; // 默认离线
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 置顶/非置顶分组并组内按时间降序
|
|||
|
|
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);
|
|||
|
|
|
|||
|
|
let sortedAll = [...pinnedConversations, ...normalConversations];
|
|||
|
|
|
|||
|
|
// 应用来自聊天页的撤回提示
|
|||
|
|
sortedAll = this.applyRecallHints(sortedAll);
|
|||
|
|
|
|||
|
|
// 基于隐藏集合过滤呈现列表
|
|||
|
|
const hiddenSet = this._getHiddenConversationSet();
|
|||
|
|
const filteredSorted = (sortedAll || []).filter(item =>
|
|||
|
|
!hiddenSet.has(String(item.conversationId || item.id || ''))
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const totalUnreadCount = filteredSorted.reduce((sum, item) => sum + (item.unreadCount || 0), 0);
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
conversations: sortedAll,
|
|||
|
|
pinnedConversations: filteredSorted.filter(i => i.isPinned),
|
|||
|
|
normalConversations: filteredSorted.filter(i => !i.isPinned),
|
|||
|
|
totalUnreadCount,
|
|||
|
|
filteredConversations: filteredSorted
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 更新角标
|
|||
|
|
this.updateTabBadge();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 应用撤回提示(来自 chat 页存储),TTL=2分钟
|
|||
|
|
applyRecallHints(list) {
|
|||
|
|
try {
|
|||
|
|
const store = wx.getStorageSync('recallHints') || {};
|
|||
|
|
const now = Date.now();
|
|||
|
|
const TTL = 2 * 60 * 1000; // 2分钟
|
|||
|
|
|
|||
|
|
return list.map(conv => {
|
|||
|
|
const cid = conv.conversationId || conv.id;
|
|||
|
|
if (!cid) return conv;
|
|||
|
|
|
|||
|
|
const hint = store[cid];
|
|||
|
|
if (hint && (now - hint.timestamp < TTL)) {
|
|||
|
|
return {
|
|||
|
|
...conv,
|
|||
|
|
lastMessage: '已撤回',
|
|||
|
|
lastMessageType: 'system'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
return conv;
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('应用撤回提示失败:', e);
|
|||
|
|
return list;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 刷新会话列表
|
|||
|
|
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.processConversations(this.data.conversations);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔥 本地会话列表过滤(快速响应)
|
|||
|
|
this.fuzzyFilterConversations(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);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 🔥 带搜索关键词打开聊天
|
|||
|
|
openChatWithSearchKeyword(conversation, searchKeyword) {
|
|||
|
|
if (!conversation.targetId) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '会话信息不完整',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const chatType = conversation.chatType !== undefined ? conversation.chatType : 0;
|
|||
|
|
|
|||
|
|
const params = {
|
|||
|
|
conversationId: conversation.conversationId,
|
|||
|
|
targetId: conversation.targetId,
|
|||
|
|
name: encodeURIComponent(conversation.name),
|
|||
|
|
chatType: chatType
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const kw = (searchKeyword || '').trim();
|
|||
|
|
if (kw) {
|
|||
|
|
params.searchKeyword = encodeURIComponent(kw);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const queryString = Object.keys(params)
|
|||
|
|
.map(key => `${key}=${params[key]}`)
|
|||
|
|
.join('&');
|
|||
|
|
|
|||
|
|
wx.navigateTo({
|
|||
|
|
url: `/pages/message/chat/chat?${queryString}`
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 本地模糊过滤
|
|||
|
|
fuzzyFilterConversations(keyword) {
|
|||
|
|
if (!keyword) {
|
|||
|
|
this.processConversations(this.data.conversations);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const q = (keyword || '').toLowerCase();
|
|||
|
|
|
|||
|
|
const subseqScore = (text, query) => {
|
|||
|
|
if (!text || !query) return 0;
|
|||
|
|
text = text.toLowerCase();
|
|||
|
|
let ti = 0, qi = 0, matches = 0;
|
|||
|
|
while (ti < text.length && qi < query.length) {
|
|||
|
|
if (text[ti] === query[qi]) {
|
|||
|
|
matches++;
|
|||
|
|
qi++;
|
|||
|
|
}
|
|||
|
|
ti++;
|
|||
|
|
}
|
|||
|
|
return qi === query.length ? matches / text.length : 0;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const samples = this.data.conversationSamples || {};
|
|||
|
|
const list = this.data.conversations || [];
|
|||
|
|
|
|||
|
|
const withScore = list.map(item => {
|
|||
|
|
const nameScore = subseqScore(item.name || '', q) * 3;
|
|||
|
|
const lastMsgScore = subseqScore(item.lastMessage || '', q) * 2;
|
|||
|
|
const sampleTexts = samples[item.conversationId] || [];
|
|||
|
|
const sampleScore = sampleTexts.reduce((acc, txt) => Math.max(acc, subseqScore(txt, q)), 0);
|
|||
|
|
const totalScore = nameScore + lastMsgScore + sampleScore;
|
|||
|
|
return { item, score: totalScore };
|
|||
|
|
}).filter(x => x.score > 0);
|
|||
|
|
|
|||
|
|
withScore.sort((a, b) => {
|
|||
|
|
if (Math.abs(a.score - b.score) < 0.01) {
|
|||
|
|
return b.item._ts - a.item._ts;
|
|||
|
|
}
|
|||
|
|
return b.score - a.score;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
let filtered = withScore.map(x => x.item);
|
|||
|
|
if (filtered.length === 0) {
|
|||
|
|
filtered = list.filter(item => {
|
|||
|
|
const n = (item.name || '').toLowerCase();
|
|||
|
|
const m = (item.lastMessage || '').toLowerCase();
|
|||
|
|
return n.includes(q) || m.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 || [];
|
|||
|
|
|
|||
|
|
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;
|
|||
|
|
const loading = this.data.sampleLoading[cid];
|
|||
|
|
const existing = this.data.conversationSamples[cid];
|
|||
|
|
|
|||
|
|
if (!loading && !existing) {
|
|||
|
|
tasks.push(this.fetchConversationSample(conv));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (tasks.length) {
|
|||
|
|
await Promise.allSettled(tasks);
|
|||
|
|
this.fuzzyFilterConversations(this.data.lastSearchKeyword);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async fetchConversationSample(conv) {
|
|||
|
|
const cid = conv.conversationId;
|
|||
|
|
try {
|
|||
|
|
this.setData({
|
|||
|
|
[`sampleLoading.${cid}`]: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 这里可以调用 NIM SDK 获取历史消息
|
|||
|
|
// 暂时留空,等待实现
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
[`conversationSamples.${cid}`]: [],
|
|||
|
|
[`sampleLoading.${cid}`]: false
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('抓取会话样本失败:', e);
|
|||
|
|
this.setData({
|
|||
|
|
[`sampleLoading.${cid}`]: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 打开聊天
|
|||
|
|
openChat(e) {
|
|||
|
|
// 如果正在长按,忽略单击事件
|
|||
|
|
if (this.data.longPressing) {
|
|||
|
|
console.log('长按中,忽略单击事件');
|
|||
|
|
this.setData({ longPressing: false });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const conversation = e.currentTarget.dataset.conversation;
|
|||
|
|
|
|||
|
|
if (!conversation.targetId) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '会话信息不完整',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const chatType = conversation.chatType !== undefined ? conversation.chatType : 0;
|
|||
|
|
|
|||
|
|
const params = {
|
|||
|
|
conversationId: conversation.conversationId,
|
|||
|
|
targetId: conversation.targetId,
|
|||
|
|
name: encodeURIComponent(conversation.name),
|
|||
|
|
chatType: chatType
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const kw = (this.data.searchKeyword || '').trim();
|
|||
|
|
if (kw) {
|
|||
|
|
params.searchKeyword = encodeURIComponent(kw);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const queryString = Object.keys(params)
|
|||
|
|
.map(key => `${key}=${params[key]}`)
|
|||
|
|
.join('&');
|
|||
|
|
|
|||
|
|
wx.navigateTo({
|
|||
|
|
url: `/pages/message/chat/chat?${queryString}`
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 显示会话选项
|
|||
|
|
showConversationOptions(e) {
|
|||
|
|
// 设置长按标记,防止长按后触发单击
|
|||
|
|
this.setData({ longPressing: true });
|
|||
|
|
|
|||
|
|
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.hideConversation(conversation.conversationId);
|
|||
|
|
break;
|
|||
|
|
case 3:
|
|||
|
|
this.deleteConversation(conversation.conversationId);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 标记会话已读
|
|||
|
|
async markConversationRead(conversationId) {
|
|||
|
|
try {
|
|||
|
|
await nimConversationManager.markConversationRead(conversationId);
|
|||
|
|
|
|||
|
|
const conversations = [...this.data.conversations];
|
|||
|
|
const index = conversations.findIndex(c => c.conversationId === conversationId);
|
|||
|
|
|
|||
|
|
if (index >= 0) {
|
|||
|
|
conversations[index].unreadCount = 0;
|
|||
|
|
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;
|
|||
|
|
wx.showLoading({ title: '处理中...' });
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const tasks = unreadIds.map(id => this.markConversationRead(id));
|
|||
|
|
await Promise.allSettled(tasks);
|
|||
|
|
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({ title: '已全部标记为已读', icon: 'success' });
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('批量标记已读失败:', err);
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({ title: '操作失败', icon: 'none' });
|
|||
|
|
} finally {
|
|||
|
|
this._markingAll = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 发起聊天入口
|
|||
|
|
startNewChat() {
|
|||
|
|
this.showMenu();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// ===== 隐藏会话 =====
|
|||
|
|
_hiddenStorageKey: 'hiddenConversationIds',
|
|||
|
|
|
|||
|
|
_getHiddenConversationSet() {
|
|||
|
|
try {
|
|||
|
|
const arr = wx.getStorageSync(this._hiddenStorageKey) || [];
|
|||
|
|
return new Set(arr);
|
|||
|
|
} catch (_) {
|
|||
|
|
return new Set();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
_saveHiddenConversationSet(set) {
|
|||
|
|
try {
|
|||
|
|
wx.setStorageSync(this._hiddenStorageKey, Array.from(set));
|
|||
|
|
} catch (_) { }
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
hideConversation(conversationId) {
|
|||
|
|
const cid = String(conversationId || '');
|
|||
|
|
if (!cid) return;
|
|||
|
|
|
|||
|
|
const set = this._getHiddenConversationSet();
|
|||
|
|
if (!set.has(cid)) set.add(cid);
|
|||
|
|
this._saveHiddenConversationSet(set);
|
|||
|
|
|
|||
|
|
this.processConversations(this.data.conversations || []);
|
|||
|
|
wx.showToast({ title: '已隐藏该聊天', icon: 'none' });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
_unhideConversationId(conversationId) {
|
|||
|
|
const cid = String(conversationId || '');
|
|||
|
|
if (!cid) return;
|
|||
|
|
|
|||
|
|
const set = this._getHiddenConversationSet();
|
|||
|
|
if (set.has(cid)) {
|
|||
|
|
set.delete(cid);
|
|||
|
|
this._saveHiddenConversationSet(set);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// ===== 会话操作 =====
|
|||
|
|
|
|||
|
|
// 置顶/取消置顶会话
|
|||
|
|
async toggleConversationTop(conversationId) {
|
|||
|
|
const conversations = [...this.data.conversations];
|
|||
|
|
const conv = conversations.find(c => c.conversationId === conversationId);
|
|||
|
|
|
|||
|
|
if (!conv) return;
|
|||
|
|
|
|||
|
|
const willPin = !conv.isPinned;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
wx.showLoading({ title: willPin ? '置顶中...' : '取消中...' });
|
|||
|
|
|
|||
|
|
await nimConversationManager.setConversationStickTop(conversationId, willPin);
|
|||
|
|
|
|||
|
|
conv.isPinned = willPin;
|
|||
|
|
conv.isTop = willPin;
|
|||
|
|
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({
|
|||
|
|
title: willPin ? '已置顶' : '已取消置顶',
|
|||
|
|
icon: 'success'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('置顶操作失败:', error);
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '操作失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 静音/取消静音会话
|
|||
|
|
async toggleConversationMute(conversationId) {
|
|||
|
|
const conversations = [...this.data.conversations];
|
|||
|
|
const conv = conversations.find(c => c.conversationId === conversationId);
|
|||
|
|
|
|||
|
|
if (!conv) return;
|
|||
|
|
|
|||
|
|
const willMute = !conv.isMuted;
|
|||
|
|
conv.isMuted = willMute;
|
|||
|
|
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
|
|||
|
|
wx.showToast({
|
|||
|
|
title: willMute ? '已设置免打扰' : '已取消免打扰',
|
|||
|
|
icon: 'success'
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 删除会话
|
|||
|
|
async deleteConversation(conversationId) {
|
|||
|
|
try {
|
|||
|
|
wx.showLoading({ title: '删除中...' });
|
|||
|
|
|
|||
|
|
await nimConversationManager.deleteConversation(conversationId);
|
|||
|
|
|
|||
|
|
const conversations = this.data.conversations.filter(
|
|||
|
|
c => c.conversationId !== conversationId
|
|||
|
|
);
|
|||
|
|
this.processConversations(conversations);
|
|||
|
|
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({ title: '已删除', icon: 'success' });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('删除会话失败:', error);
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({ title: '删除失败', icon: 'none' });
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 显示菜单
|
|||
|
|
showMenu() {
|
|||
|
|
wx.showActionSheet({
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 创建群聊
|
|||
|
|
createGroup() {
|
|||
|
|
wx.navigateTo({ url: '/subpackages/group/create-group/create-group' });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 添加好友
|
|||
|
|
addFriend() {
|
|||
|
|
wx.navigateTo({ url: '/subpackages/social/search/search' });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 扫一扫
|
|||
|
|
scanQRCode() {
|
|||
|
|
wx.scanCode({
|
|||
|
|
success: (res) => {
|
|||
|
|
this.handleScanResult(res.result);
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('扫码失败:', err);
|
|||
|
|
if (err.errMsg && err.errMsg.includes('cancel')) {
|
|||
|
|
// 用户取消扫码,不显示错误提示
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '扫码失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理扫码结果
|
|||
|
|
handleScanResult(result) {
|
|||
|
|
console.log('📱 扫码结果:', result);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 处理 FindMe 用户二维码格式: FINDME:{customId}
|
|||
|
|
if (result && result.startsWith('FINDME:')) {
|
|||
|
|
const customId = result.split(':')[1];
|
|||
|
|
|
|||
|
|
if (!customId) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '无效的用户二维码',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否是自己
|
|||
|
|
const app = getApp();
|
|||
|
|
const currentUser = app.globalData.userInfo?.user;
|
|||
|
|
|
|||
|
|
if (currentUser && currentUser.customId === customId) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '这是你自己的二维码',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 跳转到用户预览页(该页面会自动处理好友/非好友状态)
|
|||
|
|
wx.navigateTo({
|
|||
|
|
url: `/subpackages/social/user-preview/user-preview?customId=${customId}`,
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('跳转用户预览页失败:', err);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '打开用户资料失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 尝试解析URL格式的二维码(兼容旧格式)
|
|||
|
|
try {
|
|||
|
|
const url = new URL(result);
|
|||
|
|
const type = url.searchParams.get('type');
|
|||
|
|
const id = url.searchParams.get('id');
|
|||
|
|
|
|||
|
|
if (type === 'user' && id) {
|
|||
|
|
// 检查是否是自己
|
|||
|
|
const app = getApp();
|
|||
|
|
const currentUser = app.globalData.userInfo?.user;
|
|||
|
|
|
|||
|
|
if (currentUser && currentUser.customId === id) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '这是你自己的二维码',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
wx.navigateTo({
|
|||
|
|
url: `/subpackages/social/user-preview/user-preview?customId=${id}`
|
|||
|
|
});
|
|||
|
|
} else if (type === 'group' && id) {
|
|||
|
|
this.joinGroupByQR(id);
|
|||
|
|
} else {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '无效的二维码',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (urlError) {
|
|||
|
|
// 不是URL格式,显示无效二维码提示
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '无法识别的二维码格式',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('处理扫码结果失败:', error);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '处理二维码失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 通过二维码加入群聊
|
|||
|
|
async joinGroupByQR(groupId) {
|
|||
|
|
try {
|
|||
|
|
wx.showLoading({ title: '加入中...' });
|
|||
|
|
|
|||
|
|
// 调用加入群组API
|
|||
|
|
// await groupAPI.joinGroup(groupId);
|
|||
|
|
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({ title: '已加入群聊', icon: 'success' });
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
wx.navigateTo({
|
|||
|
|
url: `/subpackages/group/group-info/group-info?groupId=${groupId}`
|
|||
|
|
});
|
|||
|
|
}, 1500);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加入群聊失败:', error);
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '加入失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 打开设置
|
|||
|
|
openSettings() {
|
|||
|
|
wx.navigateTo({ url: '/subpackages/settings/settingss/settingss' });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 页面显示
|
|||
|
|
onShow: function () {
|
|||
|
|
console.log('消息页面显示');
|
|||
|
|
|
|||
|
|
// 设置tabBar选中状态为"聊天"(索引2)
|
|||
|
|
try {
|
|||
|
|
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
|||
|
|
this.getTabBar().setData({ selected: 2 });
|
|||
|
|
}
|
|||
|
|
} catch (_) {}
|
|||
|
|
|
|||
|
|
const currentToken = apiClient.getToken();
|
|||
|
|
const app = getApp();
|
|||
|
|
const isLoggedIn = app?.globalData?.isLoggedIn || false;
|
|||
|
|
|
|||
|
|
if (!currentToken) {
|
|||
|
|
console.warn('⚠️ onShow检测到用户未登录');
|
|||
|
|
if (isLoggedIn) {
|
|||
|
|
this.redirectToLogin('onShow');
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔥 检查并重连 NIM(防止从后台恢复后 NIM 断开)
|
|||
|
|
this.checkAndReconnectNim();
|
|||
|
|
|
|||
|
|
// 刷新会话列表
|
|||
|
|
this.refreshConversations();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 页面隐藏
|
|||
|
|
onHide: function () {
|
|||
|
|
console.log('消息页面隐藏');
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 页面卸载
|
|||
|
|
onUnload: function () {
|
|||
|
|
console.log('消息页面卸载');
|
|||
|
|
|
|||
|
|
// 移除 NIM 监听器
|
|||
|
|
if (this.data.nimReady) {
|
|||
|
|
nimConversationManager.off('conversation_created');
|
|||
|
|
nimConversationManager.off('conversation_deleted');
|
|||
|
|
nimConversationManager.off('conversation_changed');
|
|||
|
|
nimConversationManager.off('new_message');
|
|||
|
|
nimConversationManager.off('message_revoked');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|