fix(im): 修复账号切换时的异步状态竞态

This commit is contained in:
YunaiV
2026-07-18 09:46:52 +08:00
parent 9cc0fff442
commit b3c5f6d798

View File

@@ -6,8 +6,16 @@ import { useRoute } from 'vue-router';
import { preferences } from '@vben/preferences';
import { getCurrentUserId } from '#/views/im/utils/auth';
import { ImConversationType } from '../utils/constants';
import { initDb, stopRequests, StorageKeys } from '../utils/db';
import {
getDbSession,
initDb,
isCurrentDbSession,
stopRequests,
StorageKeys,
} from '../utils/db';
import { ContextMenu, ToolBar } from './components';
import { GroupInfoCard } from './components/group';
import { RtcCallContainer } from './components/rtc';
@@ -39,9 +47,21 @@ const { pullOnce, cancelPull } = useMessagePuller();
const { readActive, syncPrivateReadStatus } = useMessageSender();
const voicePlayer = useVoicePlayer();
const childRouteReady = ref(false); // 子路由是否可挂载
let disposed = false; // 当前 IM 主壳是否已经卸载
/** 判断当前首页初始化任务仍属于本组件与登录账号 */
function isInitializationActive(userId: number, session?: number) {
return (
!disposed &&
getCurrentUserId() === userId &&
(session === undefined || isCurrentDbSession(session))
);
}
/** 初始化:先吃本地缓存让首屏立即渲染,再远端刷新最新数据,最后建实时通信拉离线消息 */
onMounted(async () => {
const userId = getCurrentUserId();
let session: number | undefined;
// 0.1 系统表情包后台预拉:独立链路与首屏 IDB / 远端拉取并发,消除表情面板首次展开白屏;失败仅记日志,不阻塞主流程
void faceStore
.ensureFacePackList()
@@ -51,6 +71,10 @@ onMounted(async () => {
try {
// 1.2 打开当前用户 IM DB
await initDb();
session = getDbSession();
if (!isInitializationActive(userId, session)) {
return;
}
// 1.3 多个 store 并发从 IDB 读取本地缓存
const cacheResults = await Promise.all([
conversationStore.loadConversationList(),
@@ -60,6 +84,9 @@ onMounted(async () => {
channelStore.loadChannelList(),
groupRequestStore.loadGroupRequestList(),
]);
if (!isInitializationActive(userId, session)) {
return;
}
const hasFriendRows = cacheResults[2];
const hasGroupRows = cacheResults[3];
const hasChannelRows = cacheResults[4];
@@ -102,6 +129,9 @@ onMounted(async () => {
// 2.4 执行加载
if (requiredFetches.length > 0) {
await Promise.all(requiredFetches);
if (!isInitializationActive(userId, session)) {
return;
}
}
// 2.5 好友申请增量补偿:首登也要跑,离线期间好友申请变更不会影响好友主表
@@ -111,12 +141,22 @@ onMounted(async () => {
// 3. 会话读位置先补偿,消息入库时可直接过滤已读历史消息
await conversationStore
.pullConversationReads()
.catch((error) => console.warn('[IM] 拉取会话读位置失败', error));
.pullConversationReads(() => isInitializationActive(userId, session))
.catch((error) => {
if (isInitializationActive(userId, session)) {
console.warn('[IM] 拉取会话读位置失败', error);
}
});
if (!isInitializationActive(userId, session)) {
return;
}
// 4. 实时通信:建 WebSocket 长连接 + 拉离线消息pullOnce finally 把 loading 归位)
webSocketStore.connect();
await pullOnce();
if (!isInitializationActive(userId, session)) {
return;
}
// 5. 默认选中第一个会话;若置顶分组处于折叠态,需跳过被折叠隐藏的置顶项,避免自动展开折叠
const sorted = conversationStore.getSortedConversationList;
@@ -125,6 +165,9 @@ onMounted(async () => {
conversationStore.setActiveConversation(firstVisible);
}
} catch (error) {
if (!isInitializationActive(userId, session)) {
return;
}
// 1. 首拉失败:手动复位 loadingpullOnce 没跑到,它的 finally 兜不到这里),否则后续会话列表写入全被早 return 阻断
// 2. WebSocket 不在这里 disconnect——路由离开会走 onUnmounted 自然清理,用户也可以刷新重试
conversationStore.loading = false;
@@ -164,6 +207,7 @@ window.addEventListener('beforeunload', onBeforeUnload);
/** 离开 IM 主壳:取消 pull、断开 WebSocket、保存草稿、停止语音、解绑 unload并结束当前 IM session */
onUnmounted(() => {
disposed = true;
cancelPull();
webSocketStore.disconnect();
conversationStore.flushConversationDraftSave();