mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat: 一些非常帅气的优化
This commit is contained in:
@@ -286,6 +286,11 @@ function ChatPage(_props: ChatPageProps) {
|
||||
setSessions
|
||||
])
|
||||
|
||||
// 同步 currentSessionId 到 ref
|
||||
useEffect(() => {
|
||||
currentSessionRef.current = currentSessionId
|
||||
}, [currentSessionId])
|
||||
|
||||
// 加载会话列表(优化:先返回基础数据,异步加载联系人信息)
|
||||
const loadSessions = async (options?: { silent?: boolean }) => {
|
||||
if (options?.silent) {
|
||||
@@ -301,6 +306,19 @@ function ChatPage(_props: ChatPageProps) {
|
||||
const nextSessions = options?.silent ? mergeSessions(sessionsArray) : sessionsArray
|
||||
// 确保 nextSessions 也是数组
|
||||
if (Array.isArray(nextSessions)) {
|
||||
// 【核心优化】检查当前会话是否有更新(通过 lastTimestamp 对比)
|
||||
const currentId = currentSessionRef.current
|
||||
if (currentId) {
|
||||
const newSession = nextSessions.find(s => s.username === currentId)
|
||||
const oldSession = sessionsRef.current.find(s => s.username === currentId)
|
||||
|
||||
// 如果会话存在且时间戳变大(有新消息)或者之前没有该会话
|
||||
if (newSession && (!oldSession || newSession.lastTimestamp > oldSession.lastTimestamp)) {
|
||||
console.log(`[Frontend] Detected update for current session ${currentId}, refreshing messages...`)
|
||||
void handleIncrementalRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
setSessions(nextSessions)
|
||||
// 立即启动联系人信息加载,不再延迟 500ms
|
||||
void enrichSessionsContactInfo(nextSessions)
|
||||
@@ -330,14 +348,14 @@ function ChatPage(_props: ChatPageProps) {
|
||||
|
||||
// 防止重复加载
|
||||
if (isEnrichingRef.current) {
|
||||
console.log('[性能监控] 联系人信息正在加载中,跳过重复请求')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
isEnrichingRef.current = true
|
||||
enrichCancelledRef.current = false
|
||||
|
||||
console.log(`[性能监控] 开始加载联系人信息,会话数: ${sessions.length}`)
|
||||
|
||||
const totalStart = performance.now()
|
||||
|
||||
// 移除初始 500ms 延迟,让后台加载与 UI 渲染并行
|
||||
@@ -352,12 +370,12 @@ function ChatPage(_props: ChatPageProps) {
|
||||
// 找出需要加载联系人信息的会话(没有头像或者没有显示名称的)
|
||||
const needEnrich = sessions.filter(s => !s.avatarUrl || !s.displayName || s.displayName === s.username)
|
||||
if (needEnrich.length === 0) {
|
||||
console.log('[性能监控] 所有联系人信息已缓存,跳过加载')
|
||||
|
||||
isEnrichingRef.current = false
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`[性能监控] 需要加载的联系人信息: ${needEnrich.length} 个`)
|
||||
|
||||
|
||||
// 进一步减少批次大小,每批3个,避免DLL调用阻塞
|
||||
const batchSize = 3
|
||||
@@ -366,7 +384,7 @@ function ChatPage(_props: ChatPageProps) {
|
||||
for (let i = 0; i < needEnrich.length; i += batchSize) {
|
||||
// 如果正在滚动,暂停加载
|
||||
if (isScrollingRef.current) {
|
||||
console.log('[性能监控] 检测到滚动,暂停加载联系人信息')
|
||||
|
||||
// 等待滚动结束
|
||||
while (isScrollingRef.current && !enrichCancelledRef.current) {
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
@@ -410,9 +428,9 @@ function ChatPage(_props: ChatPageProps) {
|
||||
|
||||
const totalTime = performance.now() - totalStart
|
||||
if (!enrichCancelledRef.current) {
|
||||
console.log(`[性能监控] 联系人信息加载完成,总耗时: ${totalTime.toFixed(2)}ms, 已加载: ${loadedCount}/${needEnrich.length}`)
|
||||
|
||||
} else {
|
||||
console.log(`[性能监控] 联系人信息加载被取消,已加载: ${loadedCount}/${needEnrich.length}`)
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载联系人信息失败:', e)
|
||||
@@ -514,7 +532,7 @@ function ChatPage(_props: ChatPageProps) {
|
||||
|
||||
// 如果是自己的信息且当前个人头像为空,同步更新
|
||||
if (myWxid && username === myWxid && contact.avatarUrl && !myAvatarUrl) {
|
||||
console.log('[ChatPage] 从联系人同步获取到个人头像')
|
||||
|
||||
setMyAvatarUrl(contact.avatarUrl)
|
||||
}
|
||||
|
||||
@@ -542,6 +560,50 @@ function ChatPage(_props: ChatPageProps) {
|
||||
|
||||
// 刷新当前会话消息(增量更新新消息)
|
||||
const [isRefreshingMessages, setIsRefreshingMessages] = useState(false)
|
||||
|
||||
/**
|
||||
* 极速增量刷新:基于最后一条消息时间戳,获取后续新消息
|
||||
* (由用户建议:记住上一条消息时间,自动取之后的并渲染,然后后台兜底全量同步)
|
||||
*/
|
||||
const handleIncrementalRefresh = async () => {
|
||||
if (!currentSessionId || isRefreshingMessages) return
|
||||
|
||||
// 找出当前已渲染消息中的最大时间戳
|
||||
const lastMsg = messages[messages.length - 1]
|
||||
const minTime = lastMsg?.createTime || 0
|
||||
|
||||
// 1. 优先执行增量查询并渲染(第一步)
|
||||
try {
|
||||
const result = await (window.electronAPI.chat as any).getNewMessages(currentSessionId, minTime) as {
|
||||
success: boolean;
|
||||
messages?: Message[];
|
||||
error?: string
|
||||
}
|
||||
|
||||
if (result.success && result.messages && result.messages.length > 0) {
|
||||
// 过滤去重
|
||||
const existingKeys = new Set(messages.map(getMessageKey))
|
||||
const newOnes = result.messages.filter(m => !existingKeys.has(getMessageKey(m)))
|
||||
|
||||
if (newOnes.length > 0) {
|
||||
appendMessages(newOnes, false)
|
||||
flashNewMessages(newOnes.map(getMessageKey))
|
||||
// 滚动到底部
|
||||
requestAnimationFrame(() => {
|
||||
if (messageListRef.current) {
|
||||
messageListRef.current.scrollTop = messageListRef.current.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[IncrementalRefresh] 失败,将依赖全量同步兜底:', e)
|
||||
}
|
||||
|
||||
// 2. 后台兜底:执行之前的完整游标刷新,确保没有遗漏(比如跨库的消息)
|
||||
void handleRefreshMessages()
|
||||
}
|
||||
|
||||
const handleRefreshMessages = async () => {
|
||||
if (!currentSessionId || isRefreshingMessages) return
|
||||
setJumpStartTime(0)
|
||||
@@ -584,6 +646,31 @@ function ChatPage(_props: ChatPageProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据库变更实时刷新
|
||||
useEffect(() => {
|
||||
const handleDbChange = (_event: any, data: { type: string; json: string }) => {
|
||||
try {
|
||||
const payload = JSON.parse(data.json)
|
||||
const tableName = payload.table
|
||||
|
||||
// 会话列表更新(主要靠这个触发,因为 wcdb_api 已经只监控 session 了)
|
||||
if (tableName === 'Session' || tableName === 'session') {
|
||||
void loadSessions({ silent: true })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析数据库变更通知失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
if (window.electronAPI.chat.onWcdbChange) {
|
||||
const removeListener = window.electronAPI.chat.onWcdbChange(handleDbChange)
|
||||
return () => {
|
||||
removeListener()
|
||||
}
|
||||
}
|
||||
return () => { }
|
||||
}, [loadSessions, handleRefreshMessages])
|
||||
|
||||
// 加载消息
|
||||
const loadMessages = async (sessionId: string, offset = 0, startTime = 0, endTime = 0) => {
|
||||
const listEl = messageListRef.current
|
||||
@@ -621,7 +708,7 @@ function ChatPage(_props: ChatPageProps) {
|
||||
.map(m => m.senderUsername as string)
|
||||
)]
|
||||
if (unknownSenders.length > 0) {
|
||||
console.log(`[性能监控] 预取消息发送者信息: ${unknownSenders.length} 个`)
|
||||
|
||||
// 在批量请求前,先将这些发送者标记为加载中,防止 MessageBubble 触发重复请求
|
||||
const batchPromise = loadContactInfoBatch(unknownSenders)
|
||||
unknownSenders.forEach(username => {
|
||||
@@ -1549,23 +1636,13 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
useEffect(() => {
|
||||
if (!isVideo) return
|
||||
|
||||
console.log('[Video Debug] Full message object:', JSON.stringify(message, null, 2))
|
||||
console.log('[Video Debug] Message keys:', Object.keys(message))
|
||||
console.log('[Video Debug] Message:', {
|
||||
localId: message.localId,
|
||||
localType: message.localType,
|
||||
hasVideoMd5: !!message.videoMd5,
|
||||
hasContent: !!message.content,
|
||||
hasParsedContent: !!message.parsedContent,
|
||||
hasRawContent: !!(message as any).rawContent,
|
||||
contentPreview: message.content?.substring(0, 200),
|
||||
parsedContentPreview: message.parsedContent?.substring(0, 200),
|
||||
rawContentPreview: (message as any).rawContent?.substring(0, 200)
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
// 优先使用数据库中的 videoMd5
|
||||
if (message.videoMd5) {
|
||||
console.log('[Video Debug] Using videoMd5 from message:', message.videoMd5)
|
||||
|
||||
setVideoMd5(message.videoMd5)
|
||||
return
|
||||
}
|
||||
@@ -1573,11 +1650,11 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
// 尝试从多个可能的字段获取原始内容
|
||||
const contentToUse = message.content || (message as any).rawContent || message.parsedContent
|
||||
if (contentToUse) {
|
||||
console.log('[Video Debug] Parsing MD5 from content, length:', contentToUse.length)
|
||||
|
||||
window.electronAPI.video.parseVideoMd5(contentToUse).then((result: { success: boolean; md5?: string; error?: string }) => {
|
||||
console.log('[Video Debug] Parse result:', result)
|
||||
|
||||
if (result && result.success && result.md5) {
|
||||
console.log('[Video Debug] Parsed MD5:', result.md5)
|
||||
|
||||
setVideoMd5(result.md5)
|
||||
} else {
|
||||
console.error('[Video Debug] Failed to parse MD5:', result)
|
||||
@@ -2061,11 +2138,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
String(message.localId),
|
||||
message.createTime
|
||||
)
|
||||
console.log('[ChatPage] 调用转写:', {
|
||||
sessionId: session.username,
|
||||
msgId: message.localId,
|
||||
createTime: message.createTime
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
const transcriptText = (result.transcript || '').trim()
|
||||
voiceTranscriptCache.set(voiceTranscriptCacheKey, transcriptText)
|
||||
@@ -2138,14 +2211,14 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
useEffect(() => {
|
||||
if (!isVideo || !isVideoVisible || videoInfo || videoLoading) return
|
||||
if (!videoMd5) {
|
||||
console.log('[Video Debug] No videoMd5 available yet')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[Video Debug] Loading video info for MD5:', videoMd5)
|
||||
|
||||
setVideoLoading(true)
|
||||
window.electronAPI.video.getVideoInfo(videoMd5).then((result: { success: boolean; exists: boolean; videoUrl?: string; coverUrl?: string; thumbUrl?: string; error?: string }) => {
|
||||
console.log('[Video Debug] getVideoInfo result:', result)
|
||||
|
||||
if (result && result.success) {
|
||||
setVideoInfo({
|
||||
exists: result.exists,
|
||||
@@ -2642,7 +2715,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
const fileName = message.fileName || title || '文件'
|
||||
const fileSize = message.fileSize
|
||||
const fileExt = message.fileExt || fileName.split('.').pop()?.toLowerCase() || ''
|
||||
|
||||
|
||||
// 根据扩展名选择图标
|
||||
const getFileIcon = () => {
|
||||
const archiveExts = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2']
|
||||
@@ -2662,7 +2735,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="file-message">
|
||||
<div className="file-icon">
|
||||
@@ -2682,10 +2755,10 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
if (appMsgType === '2000') {
|
||||
try {
|
||||
const content = message.rawContent || message.content || message.parsedContent || ''
|
||||
|
||||
|
||||
// 添加调试日志
|
||||
console.log('[Transfer Debug] Raw content:', content.substring(0, 500))
|
||||
|
||||
|
||||
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(content, 'text/xml')
|
||||
|
||||
@@ -2693,11 +2766,11 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
const payMemo = doc.querySelector('pay_memo')?.textContent || ''
|
||||
const paysubtype = doc.querySelector('paysubtype')?.textContent || '1'
|
||||
|
||||
console.log('[Transfer Debug] Parsed:', { feedesc, payMemo, paysubtype, title })
|
||||
|
||||
|
||||
// paysubtype: 1=待收款, 3=已收款
|
||||
const isReceived = paysubtype === '3'
|
||||
|
||||
|
||||
// 如果 feedesc 为空,使用 title 作为降级
|
||||
const displayAmount = feedesc || title || '微信转账'
|
||||
|
||||
@@ -2743,7 +2816,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
||||
<div className="miniapp-message">
|
||||
<div className="miniapp-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="miniapp-info">
|
||||
|
||||
Reference in New Issue
Block a user