fix(im): 修复账号切换时的异步状态竞态
This commit is contained in:
@@ -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. 首拉失败:手动复位 loading(pullOnce 没跑到,它的 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();
|
||||
|
||||
Reference in New Issue
Block a user