436 lines
17 KiB
JavaScript
436 lines
17 KiB
JavaScript
// pages/xiaoxi/xiaoxi.js
|
||
const app = getApp();
|
||
import { formatDate } from '../../static/lib/utils';
|
||
const { jianquanxian } = require('../../utils/imAuth/jianquanxian');
|
||
import { enrichGroupConversation, isOrderGroupConversation, sortConversations, recordConversationOpen } from '../../utils/group-chat.js';
|
||
import { loadGroupMetaCache } from '../../utils/im-user.js';
|
||
|
||
Page({
|
||
data: {
|
||
allConversations: [],
|
||
filteredConversations: [],
|
||
activeTab: 0,
|
||
tabUnread: [0, 0, 0],
|
||
displayCount: 4,
|
||
searchKeyword: '',
|
||
actionPopup: { visible: false, conversation: null },
|
||
currentUser: null,
|
||
notificationMuted: false,
|
||
scrollHeight: 0
|
||
},
|
||
|
||
_permissionSeq: 0,
|
||
|
||
onLoad() {
|
||
this.calculateScrollHeight();
|
||
const muted = wx.getStorageSync('notificationMuted') || false;
|
||
this.setData({ notificationMuted: muted });
|
||
loadGroupMetaCache(app);
|
||
this._onGlobalConvUpdated = (result) => {
|
||
const content = result?.content || result;
|
||
if (content && content.conversations) {
|
||
this.renderConversations(content);
|
||
}
|
||
};
|
||
app.on('conversationsUpdated', this._onGlobalConvUpdated);
|
||
app.on('unreadCountChanged', this._onGlobalConvUpdated);
|
||
},
|
||
|
||
onUnload() {
|
||
if (this._onGlobalConvUpdated) {
|
||
app.off('conversationsUpdated', this._onGlobalConvUpdated);
|
||
app.off('unreadCountChanged', this._onGlobalConvUpdated);
|
||
}
|
||
},
|
||
|
||
onShow() {
|
||
if (!this.checkLoginStatus()) return;
|
||
loadGroupMetaCache(app);
|
||
if (app.globalData.messageManager?.unreadTotal != null) {
|
||
app.emitEvent('tabBarBadgeChanged', {
|
||
badgeText: app.globalData.messageManager.unreadTotal > 0
|
||
? String(app.globalData.messageManager.unreadTotal) : ''
|
||
});
|
||
}
|
||
this.checkPermissionAndAutoConnect();
|
||
},
|
||
|
||
// ❌ 原版注释保留:删除所有清理逻辑,监听器永久存活
|
||
onHide() {},
|
||
onUnload() {},
|
||
|
||
// ========== 鉴权检查 ==========
|
||
async checkPermissionAndAutoConnect() {
|
||
const seq = ++this._permissionSeq;
|
||
let quanxian;
|
||
try {
|
||
quanxian = await jianquanxian(app);
|
||
} catch (e) {
|
||
return;
|
||
}
|
||
if (seq !== this._permissionSeq) return;
|
||
|
||
if (!quanxian.allowed) {
|
||
const currentRole = app.globalData.currentRole || 'normal';
|
||
let content = '您没有权限使用消息功能';
|
||
if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能';
|
||
wx.showModal({
|
||
title: '权限不足',
|
||
content,
|
||
showCancel: false,
|
||
confirmText: '我知道了'
|
||
});
|
||
return;
|
||
}
|
||
this.autoConnect();
|
||
},
|
||
|
||
// ========== 登录状态检查 ==========
|
||
checkLoginStatus() {
|
||
const uid = wx.getStorageSync('uid');
|
||
if (!uid) {
|
||
wx.showToast({ title: '请先登录', icon: 'none' });
|
||
setTimeout(() => { wx.redirectTo({ url: '/pages/wode/wode' }); }, 1500);
|
||
return false;
|
||
}
|
||
let currentUser = app.globalData.currentUser;
|
||
if (!currentUser) {
|
||
currentUser = {
|
||
id: uid,
|
||
name: '用户' + uid.substring(0, 6),
|
||
avatar: app.globalData.ossImageUrl + app.globalData.morentouxiang
|
||
};
|
||
app.globalData.currentUser = currentUser;
|
||
}
|
||
this.setData({ currentUser });
|
||
return true;
|
||
},
|
||
|
||
// ========== 自动连接(复用全局连接,避免反复 disconnect) ==========
|
||
autoConnect() {
|
||
const role = app.globalData.currentRole || 'normal';
|
||
const uid = wx.getStorageSync('uid');
|
||
const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' };
|
||
const targetUserId = (prefixMap[role] || 'Boss') + uid;
|
||
|
||
const onReady = () => {
|
||
this.setupConversationListener();
|
||
this.loadConversations();
|
||
};
|
||
|
||
const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
|
||
const currentUserId = wx.goEasy?.im?.userId;
|
||
|
||
if ((status === 'connected' || status === 'reconnected') && currentUserId === targetUserId) {
|
||
onReady();
|
||
return;
|
||
}
|
||
|
||
if (currentUserId && currentUserId !== targetUserId && app.disconnectGoEasy) {
|
||
app.disconnectGoEasy().then(() => {
|
||
app.connectWithIdentity(role, targetUserId, true).then(onReady).catch(() => {});
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!app.connectWithIdentity) {
|
||
this.connectGoEasy(targetUserId);
|
||
return;
|
||
}
|
||
|
||
app.connectWithIdentity(role, targetUserId, true).then(onReady).catch(() => {
|
||
const s = wx.goEasy?.getConnectionStatus?.();
|
||
if (s === 'connected' || s === 'reconnected') onReady();
|
||
});
|
||
},
|
||
|
||
connectGoEasy(userId) {
|
||
const { currentUser } = this.data;
|
||
if (!currentUser) return;
|
||
wx.goEasy.connect({
|
||
id: userId,
|
||
data: {
|
||
name: currentUser.name,
|
||
avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang)
|
||
},
|
||
onSuccess: () => {
|
||
try {
|
||
const connection = {
|
||
userId: userId,
|
||
identityType: app.globalData.currentRole,
|
||
lastConnectTime: Date.now(),
|
||
autoReconnect: true,
|
||
status: 'connected'
|
||
};
|
||
wx.setStorageSync('savedGoEasyConnection', JSON.stringify(connection));
|
||
wx.setStorageSync('goEasyUserId', userId);
|
||
} catch (e) {}
|
||
this.setupConversationListener();
|
||
this.loadConversations();
|
||
},
|
||
onFailed: (error) => {
|
||
if (error.code === 408) {
|
||
this.setupConversationListener();
|
||
this.loadConversations();
|
||
} else {
|
||
wx.showToast({ title: '连接失败,请重试', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// ========== 会话监听(永不清理) ==========
|
||
setupConversationListener() {
|
||
if (this.conversationsUpdatedListener) {
|
||
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
|
||
}
|
||
this.conversationsUpdatedListener = (result) => {
|
||
this.renderConversations(result);
|
||
};
|
||
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
|
||
},
|
||
|
||
loadConversations() {
|
||
wx.goEasy.im.latestConversations({
|
||
onSuccess: (result) => {
|
||
this.renderConversations(result.content);
|
||
},
|
||
onFailed: () => {
|
||
wx.showToast({ title: '获取会话失败', icon: 'none' });
|
||
}
|
||
});
|
||
},
|
||
|
||
// ========== 会话渲染与排序 ==========
|
||
renderConversations(content) {
|
||
if (!content || !content.conversations) {
|
||
this.setData({ allConversations: [] }, () => this.applyFilter());
|
||
return;
|
||
}
|
||
let list = content.conversations;
|
||
const defaultAvatar = app.globalData.ossImageUrl + app.globalData.morentouxiang;
|
||
const myGoEasyId = wx.goEasy?.im?.userId || '';
|
||
const ossBase = app.globalData.ossImageUrl || '';
|
||
const groupMeta = app.globalData.groupInfoMap || {};
|
||
|
||
list.forEach(item => {
|
||
if (item.type === 'group') {
|
||
const meta = groupMeta[item.groupId];
|
||
if (meta) {
|
||
if (!item.data) item.data = {};
|
||
Object.assign(item.data, meta);
|
||
}
|
||
if (item.data && item.data.dashouGoEasyId) {
|
||
if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {};
|
||
app.globalData.groupInfoMap[item.groupId] = {
|
||
...app.globalData.groupInfoMap[item.groupId],
|
||
...item.data,
|
||
};
|
||
}
|
||
enrichGroupConversation(item, myGoEasyId, defaultAvatar, ossBase);
|
||
} else if (item.data && item.data.avatar) {
|
||
if (!item.data.avatar.startsWith('http')) {
|
||
item.data.avatar = ossBase + item.data.avatar;
|
||
}
|
||
} else if (item.data) {
|
||
item.data.avatar = defaultAvatar;
|
||
}
|
||
if (item.lastMessage && item.lastMessage.timestamp) {
|
||
item.lastMessage.date = formatDate(item.lastMessage.timestamp);
|
||
}
|
||
});
|
||
|
||
const openTimes = wx.getStorageSync('conversationOpenTimes') || {};
|
||
sortConversations(list, openTimes);
|
||
|
||
const unreadTotal = content.unreadTotal !== undefined ? content.unreadTotal : list.reduce((sum, c) => sum + (c.unread || 0), 0);
|
||
if (app.globalData.messageManager) {
|
||
app.globalData.messageManager.unreadTotal = unreadTotal;
|
||
}
|
||
if (app && app.emitEvent) {
|
||
app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' });
|
||
app.emitEvent('unreadCountChanged', { unreadTotal });
|
||
}
|
||
|
||
this.setData({ allConversations: list, displayCount: 4 }, () => {
|
||
this.applyFilter();
|
||
});
|
||
|
||
// 群组订阅(不重复断开连接)
|
||
this.subscribeGroupsIfNeeded(list);
|
||
},
|
||
|
||
/**
|
||
* 群组订阅(依赖:GoEasy IM)
|
||
* 根据官方文档,需要主动 subscribeGroup 才能接收群聊消息。
|
||
* 为了应对切后台、网络波动等导致的订阅丢失,这里不再缓存订阅状态,
|
||
* 每次获取到群组列表时都直接订阅,保证消息不丢失。
|
||
*/
|
||
subscribeGroupsIfNeeded(conversations) {
|
||
if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return;
|
||
|
||
const groupIds = conversations
|
||
.filter(c => c.type === 'group' && c.groupId)
|
||
.map(c => c.groupId);
|
||
|
||
if (groupIds.length === 0) return;
|
||
|
||
// 不再判断缓存,直接订阅当前所有群组
|
||
wx.goEasy.im.subscribeGroup({
|
||
groupIds: groupIds,
|
||
onSuccess: () => {
|
||
// 可选的日志输出,不影响功能
|
||
// console.log('群组订阅成功', groupIds);
|
||
},
|
||
onFailed: (error) => {
|
||
console.error('群组订阅失败:', error);
|
||
}
|
||
});
|
||
},
|
||
|
||
applyFilter() {
|
||
const { allConversations, activeTab, searchKeyword } = this.data;
|
||
let filtered = [];
|
||
if (activeTab === 0) {
|
||
filtered = allConversations.filter(c => isOrderGroupConversation(c));
|
||
} else if (activeTab === 1) {
|
||
filtered = allConversations.filter(c => c.type === 'private');
|
||
} else if (activeTab === 2) {
|
||
filtered = allConversations.filter(c => c.type === 'cs');
|
||
}
|
||
|
||
if (searchKeyword.trim()) {
|
||
const kw = searchKeyword.trim().toLowerCase();
|
||
filtered = filtered.filter(c => {
|
||
const name = (c.data && c.data.name || '').toLowerCase();
|
||
const orderId = (c.data && c.data.orderId || '').toLowerCase();
|
||
const id = (c.userId || c.groupId || '').toLowerCase();
|
||
return name.includes(kw) || id.includes(kw) || orderId.includes(kw);
|
||
});
|
||
}
|
||
|
||
const tabUnread = [0, 0, 0];
|
||
allConversations.forEach(c => {
|
||
if (isOrderGroupConversation(c)) tabUnread[0] += c.unread || 0;
|
||
else if (c.type === 'private') tabUnread[1] += c.unread || 0;
|
||
else if (c.type === 'cs') tabUnread[2] += c.unread || 0;
|
||
});
|
||
|
||
this.setData({ filteredConversations: filtered, tabUnread });
|
||
},
|
||
|
||
switchTab(e) {
|
||
const index = parseInt(e.currentTarget.dataset.index);
|
||
this.setData({ activeTab: index, displayCount: 4, searchKeyword: '' }, () => this.applyFilter());
|
||
},
|
||
|
||
onSearchInput(e) {
|
||
this.setData({ searchKeyword: e.detail.value, displayCount: 4 }, () => this.applyFilter());
|
||
},
|
||
|
||
onScrollToLower() {
|
||
const { displayCount, filteredConversations } = this.data;
|
||
if (displayCount < filteredConversations.length) {
|
||
this.setData({ displayCount: Math.min(displayCount + 4, filteredConversations.length) });
|
||
}
|
||
},
|
||
|
||
onRefresh() {
|
||
this.loadConversations();
|
||
},
|
||
|
||
chat(e) {
|
||
const conversation = e.currentTarget.dataset.conversation;
|
||
if (!conversation) return;
|
||
recordConversationOpen(conversation);
|
||
|
||
if (conversation.type === 'private') {
|
||
const param = {
|
||
toUserId: conversation.userId,
|
||
toName: conversation.data.name,
|
||
toAvatar: conversation.data.avatar
|
||
};
|
||
wx.navigateTo({ url: '/pages/liaotian/liaotian?data=' + encodeURIComponent(JSON.stringify(param)) });
|
||
} else if (conversation.type === 'group') {
|
||
const param = {
|
||
groupId: conversation.groupId,
|
||
orderId: conversation.data.orderId || '',
|
||
groupName: conversation.data.name,
|
||
groupAvatar: conversation.data.avatar,
|
||
isCross: conversation.data.isCross || 0
|
||
};
|
||
wx.navigateTo({ url: '/pages/qunliaotian/qunliaotian?data=' + encodeURIComponent(JSON.stringify(param)) });
|
||
} else if (conversation.type === 'cs') {
|
||
const param = { teamId: conversation.teamId || conversation.id };
|
||
wx.navigateTo({ url: '/pages/kefuliaotian/kefuliaotian?data=' + encodeURIComponent(JSON.stringify(param)) });
|
||
}
|
||
},
|
||
|
||
chatKefu() {
|
||
const param = { teamId: 'support_team' };
|
||
wx.navigateTo({ url: '/pages/kefuliaotian/kefuliaotian?data=' + encodeURIComponent(JSON.stringify(param)) });
|
||
},
|
||
|
||
showAction(e) {
|
||
const conversation = e.currentTarget.dataset.conversation;
|
||
this.setData({ ['actionPopup.conversation']: conversation, ['actionPopup.visible']: true });
|
||
},
|
||
closeMask() {
|
||
this.setData({ ['actionPopup.visible']: false });
|
||
},
|
||
topConversation() {
|
||
let conversation = this.data.actionPopup.conversation;
|
||
if (!conversation) return;
|
||
const top = !conversation.top;
|
||
wx.showLoading({ title: '处理中...' });
|
||
if (conversation.type === 'private') {
|
||
wx.goEasy.im.topPrivateConversation({
|
||
userId: conversation.userId, top,
|
||
onSuccess: () => {
|
||
wx.hideLoading(); this.loadConversations();
|
||
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
|
||
},
|
||
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
|
||
});
|
||
} else {
|
||
wx.goEasy.im.topGroupConversation({
|
||
groupId: conversation.groupId, top,
|
||
onSuccess: () => {
|
||
wx.hideLoading(); this.loadConversations();
|
||
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
|
||
},
|
||
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
|
||
});
|
||
}
|
||
this.closeMask();
|
||
},
|
||
removeConversation() {
|
||
let conversation = this.data.actionPopup.conversation;
|
||
if (!conversation) return;
|
||
wx.showLoading({ title: '删除中...' });
|
||
wx.goEasy.im.removeConversation({
|
||
conversation: conversation,
|
||
onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: '已删除' }); },
|
||
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); }
|
||
});
|
||
this.closeMask();
|
||
},
|
||
|
||
toggleNotification() {
|
||
const newMuted = !this.data.notificationMuted;
|
||
this.setData({ notificationMuted: newMuted });
|
||
wx.setStorageSync('notificationMuted', newMuted);
|
||
wx.showToast({ title: newMuted ? '已静音' : '已开启通知', icon: 'none' });
|
||
},
|
||
|
||
calculateScrollHeight() {
|
||
const systemInfo = wx.getSystemInfoSync();
|
||
const windowHeight = systemInfo.windowHeight;
|
||
const windowWidth = systemInfo.windowWidth;
|
||
const rpxRatio = 750 / windowWidth;
|
||
const headerHeight = 150 / rpxRatio;
|
||
const tabBarHeight = 130 / rpxRatio;
|
||
const safeAreaBottom = systemInfo.safeArea ? (windowHeight - systemInfo.safeArea.bottom) : 0;
|
||
this.setData({ scrollHeight: windowHeight - headerHeight - tabBarHeight - safeAreaBottom });
|
||
}
|
||
}); |