1399 lines
No EOL
42 KiB
JavaScript
1399 lines
No EOL
42 KiB
JavaScript
// 消息页面逻辑 - 基于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);
|
||
});
|
||
}
|
||
}); |