perf(common): ⚡ optimize session lists and chat stores

This commit is contained in:
Dawn
2025-10-31 20:38:25 +08:00
parent 6ed3052a37
commit 1fb6c77f34
12 changed files with 142 additions and 68 deletions
+1 -1
View File
@@ -291,7 +291,7 @@ useMitt.on(WsResponseMessageType.INVALID_USER, (param: { uid: string }) => {
console.log('无效用户')
const data = param
// 消息列表删掉拉黑的发言
chatStore.filterUser(data.uid)
// chatStore.filterUser(data.uid)
// 群成员列表删掉拉黑的用户
groupStore.removeUserItem(data.uid)
})
+2 -3
View File
@@ -86,6 +86,7 @@
</template>
<script setup lang="ts">
import { sumBy } from 'es-toolkit'
import ContextMenu from '@/components/common/ContextMenu.vue'
import { useDownload } from '@/hooks/useDownload'
import type { FileBody } from '@/services/types'
@@ -183,9 +184,7 @@ const displayedTimeGroupedFiles = computed(() => {
})
// 计算过滤后的文件总数
const totalDisplayedFiles = computed(() =>
displayedTimeGroupedFiles.value.reduce((sum: number, group: TimeGroup) => sum + group.files.length, 0)
)
const totalDisplayedFiles = computed(() => sumBy(displayedTimeGroupedFiles.value, (group) => group.files.length))
const { downloadFile } = useDownload()
+2 -4
View File
@@ -107,6 +107,7 @@
</n-flex>
</template>
<script setup lang="ts">
import { uniq } from 'es-toolkit'
import type { NoticeItem } from '@/services/types.ts'
import { NoticeType, RequestNoticeAgreeStatus } from '@/services/types.ts'
import { useContactStore } from '@/stores/contacts.ts'
@@ -339,10 +340,7 @@ onMounted(() => {
watch(
() => applyList.value,
(newList) => {
const roomIds = newList
.filter((item) => item.roomId && Number(item.roomId) > 0)
.map((item) => item.roomId)
.filter((roomId, index, array) => array.indexOf(roomId) === index)
const roomIds = uniq(newList.filter((item) => item.roomId && Number(item.roomId) > 0).map((item) => item.roomId))
if (roomIds.length > 0) {
// 批量加载群组信息
+4 -1
View File
@@ -81,7 +81,10 @@ export const useReplaceMsg = () => {
*/
const getMessageSenderName = (message: MessageType, defaultName: string = '', roomId: string) => {
if (!message?.fromUser?.uid) return defaultName
const session = chatStore.getSession(roomId)!
const session = chatStore.getSession(roomId)
// 防御性检查:如果 session 不存在,返回默认名称
if (!session) return defaultName
if (session.type === RoomTypeEnum.GROUP) {
const user = groupStore.getUser(roomId, message.fromUser.uid)
return user?.myName || user?.name || ''
@@ -144,6 +144,7 @@
<script setup lang="ts">
import { emitTo } from '@tauri-apps/api/event'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { cloneDeep } from 'es-toolkit'
import { PluginEnum } from '@/enums'
import { pluginsList } from '@/layout/left/config.tsx'
import { usePluginsStore } from '@/stores/plugins.ts'
@@ -156,7 +157,7 @@ const { page } = storeToRefs(settingStore)
const { plugins } = storeToRefs(pluginsStore)
const isCurrently = ref(-1)
const allPlugins = ref([] as STO.Plugins<PluginEnum>[])
const pluginsLists = ref<STO.Plugins<PluginEnum>[]>(JSON.parse(JSON.stringify(pluginsList.value)))
const pluginsLists = ref<STO.Plugins<PluginEnum>[]>(cloneDeep(pluginsList.value))
const handleState = (plugin: STO.Plugins<PluginEnum>) => {
if (plugin.state === PluginEnum.INSTALLED) return
@@ -129,6 +129,7 @@
<script setup lang="ts">
import { emitTo } from '@tauri-apps/api/event'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { cloneDeep } from 'es-toolkit'
import FloatBlockList from '@/components/common/FloatBlockList.vue'
import { PluginEnum } from '@/enums'
import { pluginsList } from '@/layout/left/config.tsx'
@@ -139,7 +140,7 @@ const pluginsStore = usePluginsStore()
const { plugins } = storeToRefs(pluginsStore)
const isCurrently = ref(-1)
const allPlugins = ref([] as STO.Plugins<PluginEnum>[])
const pluginsLists = ref<STO.Plugins<PluginEnum>[]>(JSON.parse(JSON.stringify(pluginsList.value)))
const pluginsLists = ref<STO.Plugins<PluginEnum>[]>(cloneDeep(pluginsList.value))
const handleState = (plugin: STO.Plugins<PluginEnum>) => {
if (plugin.state === PluginEnum.INSTALLED) return
+2 -4
View File
@@ -111,6 +111,7 @@
</n-flex>
</template>
<script setup lang="ts">
import { uniq } from 'es-toolkit'
import type { NoticeItem } from '@/services/types.ts'
import { NoticeType, RequestNoticeAgreeStatus } from '@/services/types.ts'
import { useContactStore } from '@/stores/contacts.ts'
@@ -343,10 +344,7 @@ onMounted(() => {
watch(
() => applyList.value,
(newList) => {
const roomIds = newList
.filter((item) => item.roomId && Number(item.roomId) > 0)
.map((item) => item.roomId)
.filter((roomId, index, array) => array.indexOf(roomId) === index)
const roomIds = uniq(newList.filter((item) => item.roomId && Number(item.roomId) > 0).map((item) => item.roomId))
if (roomIds.length > 0) {
// 批量加载群组信息
+2 -5
View File
@@ -261,6 +261,7 @@
</template>
<script setup lang="ts">
import { sumBy } from 'es-toolkit'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { useUserStore } from '@/stores/user.ts'
import { useContactStore } from '@/stores/contacts.ts'
@@ -336,11 +337,7 @@ interface CommentItem {
}
// 计算属性
const unreadCount = computed(() => {
return dynamicList.value.reduce((total, feed) => {
return total + (feed.commentCount || 0)
}, 0)
})
const unreadCount = computed(() => sumBy(dynamicList.value, (feed) => feed.commentCount || 0))
// 验证发布内容是否有效(只验证文本内容)
const isPublishValid = computed(() => {
+116 -39
View File
@@ -1,6 +1,7 @@
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { info } from '@tauri-apps/plugin-log'
import { sendNotification } from '@tauri-apps/plugin-notification'
import pLimit from 'p-limit'
import { defineStore } from 'pinia'
import { useRoute } from 'vue-router'
import { ErrorType } from '@/common/exception'
@@ -51,6 +52,8 @@ export const useChatStore = defineStore(
// 会话列表
const sessionList = ref<SessionItem[]>([])
// 会话列表的快速查找 Map,通过 roomId 进行 O(1) 查找
const sessionMap = ref<Record<string, SessionItem>>({})
// 会话列表的加载状态
const sessionOptions = reactive({ isLast: false, isLoading: false, cursor: '' })
@@ -114,9 +117,13 @@ export const useChatStore = defineStore(
const isGroup = computed(() => globalStore.currentSession?.type === RoomTypeEnum.GROUP)
// 获取当前会话信息的计算属性
const currentSessionInfo = computed(() =>
sessionList.value.find((session) => session.roomId === globalStore.currentSession?.roomId)
)
const currentSessionInfo = computed(() => {
const roomId = globalStore.currentSession?.roomId
if (!roomId) return undefined
// 直接从 sessionMap 中查找(页面刷新后会自动恢复)
return sessionMap.value[roomId]
})
// 新消息计数相关的响应式数据
const newMsgCount = reactive<Record<string, { count: number; isStart: boolean }>>({})
@@ -149,7 +156,13 @@ export const useChatStore = defineStore(
return
}
const roomId = globalStore.currentSession!.roomId
// 如果 currentSession 不存在,直接返回
if (!globalStore.currentSession?.roomId) {
console.warn('[changeRoom] currentSession 未初始化')
return
}
const roomId = globalStore.currentSession.roomId
// 1. 清空当前房间的旧消息数据
if (messageMap[roomId]) {
@@ -183,10 +196,12 @@ export const useChatStore = defineStore(
}
// 标记当前会话已读
const session = sessionList.value.find((s) => s.roomId === globalStore.currentSession!.roomId)
if (session?.unreadCount) {
markSessionRead(globalStore.currentSession!.roomId)
updateTotalUnreadCount()
if (globalStore.currentSession?.roomId) {
const session = sessionMap.value[globalStore.currentSession.roomId]
if (session?.unreadCount) {
markSessionRead(globalStore.currentSession.roomId)
updateTotalUnreadCount()
}
}
// 重置当前回复的消息
@@ -209,11 +224,44 @@ export const useChatStore = defineStore(
return Object.values(messageMap[roomId]).sort((a, b) => Number(a.message.id) - Number(b.message.id))
})
// 登录之后,加载一次所有会话的消息
/**
* 登录之后,加载一次所有会话的消息
* @description
* 使用受控并发加载(p-limit),避免大量会话时阻塞 UI
* - 优先加载最近活跃的会话
* - 限制并发数为 5,平衡性能和服务器压力
* - 使用 Promise.allSettled 确保部分失败不影响其他会话
*/
const setAllSessionMsgList = async (size = pageSize) => {
await info('初始设置所有会话消息列表')
for (const session of sessionList.value) {
await getPageMsg(size, session.roomId, '', true)
if (sessionList.value.length === 0) return
// 按活跃时间排序,优先加载最近的会话
const sortedSessions = [...sessionList.value].sort((a, b) => b.activeTime - a.activeTime)
// 创建并发限制器(最多同时 5 个请求)
const limit = pLimit(5)
// 使用 p-limit 包装任务并执行
const tasks = sortedSessions.map((session) => limit(() => getPageMsg(size, session.roomId, '', true)))
// 并发执行所有任务
const results = await Promise.allSettled(tasks)
// 统计加载结果
const successCount = results.filter((r) => r.status === 'fulfilled').length
const failCount = results.filter((r) => r.status === 'rejected').length
await info(`会话消息加载完成: 成功 ${successCount}/${sortedSessions.length}, 失败 ${failCount}`)
// 记录失败的会话(可选)
if (failCount > 0) {
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.warn(`会话 ${sortedSessions[index].roomId} 消息加载失败:`, result.reason)
}
})
}
}
@@ -282,6 +330,11 @@ export const useChatStore = defineStore(
sessionList.value = [...data]
sessionOptions.isLoading = false
// 同步更新 sessionMap
for (const session of sessionList.value) {
sessionMap.value[session.roomId] = session
}
sortAndUniqueSessionList()
} catch (e) {
console.error('获取会话列表失败11:', e)
@@ -291,21 +344,30 @@ export const useChatStore = defineStore(
}
}
/** 会话列表去重并排序 */
/** 会话列表排序 */
const sortAndUniqueSessionList = () => {
const temp: Record<string, SessionItem> = {}
for (const item of sessionList.value) {
temp[item.roomId] = item
}
sessionList.value.splice(0, sessionList.value.length, ...Object.values(temp))
// const temp: Record<string, SessionItem> = {}
// for (const item of sessionList.value) {
// temp[item.roomId] = item
// }
// sessionList.value.splice(0, sessionList.value.length, ...Object.values(temp))
sessionList.value.sort((pre, cur) => cur.activeTime - pre.activeTime)
}
// 更新会话
const updateSession = (roomId: string, data: Partial<SessionItem>) => {
const index = sessionList.value.findIndex((session) => session.roomId === roomId)
if (index !== -1) {
sessionList.value[index] = { ...sessionList.value[index], ...data }
const session = sessionMap.value[roomId]
if (session) {
const updatedSession = { ...session, ...data }
// 同步更新 sessionList
const index = sessionList.value.findIndex((s) => s.roomId === roomId)
if (index !== -1) {
sessionList.value[index] = updatedSession
}
// 同步更新 sessionMap
sessionMap.value[roomId] = updatedSession
// 如果更新了免打扰状态,需要重新计算全局未读数
if ('muteNotification' in data) {
@@ -316,7 +378,8 @@ export const useChatStore = defineStore(
// 更新会话最后活跃时间, 只要更新的过程中会话不存在,那么将会话刷新出来
const updateSessionLastActiveTime = (roomId: string) => {
const session = sessionList.value.find((item) => item.roomId === roomId)
// O(1) 查找
const session = sessionMap.value[roomId]
if (session) {
Object.assign(session, { activeTime: Date.now() })
} else {
@@ -328,13 +391,19 @@ export const useChatStore = defineStore(
const addSession = async (roomId: string) => {
const resp = await getSessionDetail({ id: roomId })
sessionList.value.unshift(resp)
// 同步更新 sessionMap
sessionMap.value[roomId] = resp
sortAndUniqueSessionList()
}
// 通过房间ID获取会话信息
const getSession = (roomId: string) => {
const currentSession = roomId ? sessionList.value.find((item) => item.roomId === roomId) : sessionList.value[0]
return currentSession
if (!roomId) {
return sessionList.value[0]
}
// O(1) 查找(页面刷新后自动从持久化恢复)
return sessionMap.value[roomId]
}
// 推送消息
@@ -418,17 +487,17 @@ export const useChatStore = defineStore(
}
// 过滤掉拉黑用户的发言
const filterUser = (uid: string) => {
for (const roomId in messageMap) {
const messages = messageMap[roomId]
for (const msgId in messages) {
const msg = messages[msgId]
if (msg.fromUser.uid === uid) {
delete messages[msgId]
}
}
}
}
// const filterUser = (uid: string) => {
// for (const roomId in messageMap) {
// const messages = messageMap[roomId]
// for (const msgId in messages) {
// const msg = messages[msgId]
// if (msg.fromUser.uid === uid) {
// delete messages[msgId]
// }
// }
// }
// }
// 加载更多消息
const loadMore = async (size?: number) => {
@@ -670,7 +739,8 @@ export const useChatStore = defineStore(
// 标记已读数为 0
const markSessionRead = (roomId: string) => {
const session = sessionList.value.find((s) => s.roomId === roomId)
// O(1) 查找
const session = sessionMap.value[roomId]
if (session) {
// 更新会话的未读数
session.unreadCount = 0
@@ -687,9 +757,17 @@ export const useChatStore = defineStore(
// 删除会话
const removeSession = (roomId: string) => {
const index = sessionList.value.findIndex((session) => session.roomId === roomId)
if (index !== -1) {
sessionList.value.splice(index, 1)
const session = sessionMap.value[roomId]
if (session) {
// 从数组中删除
const index = sessionList.value.findIndex((s) => s.roomId === roomId)
if (index !== -1) {
sessionList.value.splice(index, 1)
}
// 从 map 中删除
delete sessionMap.value[roomId]
if (globalStore.currentSessionRoomId === roomId) {
globalStore.updateCurrentSessionRoomId(sessionList.value[0].roomId)
}
@@ -861,7 +939,6 @@ export const useChatStore = defineStore(
currentNewMsgCount,
loadMore,
currentMsgReply,
filterUser,
sessionList,
sessionOptions,
getSessionList,
+2 -1
View File
@@ -1,5 +1,6 @@
import { appDataDir, join, resourceDir } from '@tauri-apps/api/path'
import { BaseDirectory, exists, writeFile } from '@tauri-apps/plugin-fs'
import { sumBy } from 'es-toolkit'
import { defineStore } from 'pinia'
import { StoresEnum } from '@/enums'
import type { FilesMeta } from '@/services/types'
@@ -244,7 +245,7 @@ export const useFileDownloadStore = defineStore(
}
// 合并所有数据块
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
const totalLength = sumBy(chunks, (chunk) => chunk.length)
const fileData = new Uint8Array(totalLength)
let offset = 0
+5 -7
View File
@@ -1,6 +1,7 @@
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { info } from '@tauri-apps/plugin-log'
import { useDebounceFn } from '@vueuse/core'
import { sumBy } from 'es-toolkit'
import { NotificationTypeEnum } from '@/enums'
import type { SessionItem } from '@/services/types'
import { isMac } from '@/utils/PlatformConstants'
@@ -62,15 +63,12 @@ export class UnreadCountManager {
info('[UnreadCountManager] 计算全局未读消息计数')
// 计算总未读数
const totalUnread = sessionList.reduce((total, session) => {
// 免打扰的会话不计入全局未读数
const totalUnread = sumBy(sessionList, (session) => {
if (session.muteNotification === NotificationTypeEnum.NOT_DISTURB) {
return total
return 0
}
// 确保 unreadCount 是数字且不为负数
const unread = Math.max(0, session.unreadCount || 0)
return total + unread
}, 0)
return Math.max(0, session.unreadCount || 0)
})
// 更新全局未读计数
unReadMark.newMsgUnreadCount = totalUnread
+2 -1
View File
@@ -52,6 +52,7 @@ import { type Event, emitTo } from '@tauri-apps/api/event'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { info } from '@tauri-apps/plugin-log'
import { useDebounceFn } from '@vueuse/core'
import { sumBy } from 'es-toolkit'
import { RoomTypeEnum } from '@/enums'
import { useReplaceMsg } from '@/hooks/useReplaceMsg.ts'
import { useWindow } from '@/hooks/useWindow.ts'
@@ -275,7 +276,7 @@ onMounted(async () => {
return b.timestamp - a.timestamp
})
msgCount.value = content.value.reduce((acc, group) => acc + group.messageCount, 0)
msgCount.value = sumBy(content.value, (group) => group.messageCount)
}
})