feat: 优化会话加载速度;优化动画表现;支持中文数据库路径

This commit is contained in:
cc
2026-01-11 23:32:05 +08:00
parent 4cbce8c38f
commit e5f57c7359
5 changed files with 147 additions and 11 deletions

View File

@@ -49,6 +49,8 @@ export class WcdbService {
private wcdbGetEmoticonCdnUrl: any = null
private avatarUrlCache: Map<string, { url?: string; updatedAt: number }> = new Map()
private readonly avatarCacheTtlMs = 10 * 60 * 1000
private logTimer: NodeJS.Timeout | null = null
private lastLogTail: string | null = null
setPaths(resourcesPath: string, userDataPath: string): void {
this.resourcesPath = resourcesPath
@@ -57,6 +59,11 @@ export class WcdbService {
setLogEnabled(enabled: boolean): void {
this.logEnabled = enabled
if (this.isLogEnabled() && this.initialized) {
this.startLogPolling()
} else {
this.stopLogPolling()
}
}
/**
@@ -433,6 +440,49 @@ export class WcdbService {
}
}
private startLogPolling(): void {
if (this.logTimer || !this.isLogEnabled()) return
this.logTimer = setInterval(() => {
void this.pollLogs()
}, 2000)
}
private stopLogPolling(): void {
if (this.logTimer) {
clearInterval(this.logTimer)
this.logTimer = null
}
this.lastLogTail = null
}
private async pollLogs(): Promise<void> {
try {
if (!this.wcdbGetLogs || !this.isLogEnabled()) return
const outPtr = [null as any]
const result = this.wcdbGetLogs(outPtr)
if (result !== 0 || !outPtr[0]) return
let jsonStr = ''
try {
jsonStr = this.koffi.decode(outPtr[0], 'char', -1)
} finally {
try { this.wcdbFreeString(outPtr[0]) } catch { }
}
const logs = JSON.parse(jsonStr) as string[]
if (!Array.isArray(logs) || logs.length === 0) return
let startIdx = 0
if (this.lastLogTail) {
const idx = logs.lastIndexOf(this.lastLogTail)
if (idx >= 0) startIdx = idx + 1
}
for (let i = startIdx; i < logs.length; i += 1) {
this.writeLog(`wcdb: ${logs[i]}`)
}
this.lastLogTail = logs[logs.length - 1]
} catch (e) {
// ignore polling errors
}
}
private decodeJsonPtr(outPtr: any): string | null {
if (!outPtr) return null
try {
@@ -545,6 +595,9 @@ export class WcdbService {
console.warn('设置 wxid 失败:', e)
}
}
if (this.isLogEnabled()) {
this.startLogPolling()
}
this.writeLog(`open ok handle=${handle}`)
return true
} catch (e) {
@@ -571,6 +624,7 @@ export class WcdbService {
this.currentKey = null
this.currentWxid = null
this.initialized = false
this.stopLogPolling()
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "weflow",
"version": "1.0.3",
"version": "1.0.4",
"description": "WeFlow - 微信聊天记录查看工具",
"main": "dist-electron/main.js",
"author": "cc",

Binary file not shown.

View File

@@ -883,6 +883,23 @@
min-height: 0;
overflow: hidden;
-webkit-app-region: no-drag;
position: relative;
&.loading .message-list {
opacity: 0;
transform: translateY(8px);
pointer-events: none;
}
&.loaded .message-list {
opacity: 1;
transform: translateY(0);
}
&.loaded .loading-overlay {
opacity: 0;
pointer-events: none;
}
}
.message-list {
@@ -898,6 +915,7 @@
background-color: var(--bg-tertiary);
position: relative;
-webkit-app-region: no-drag !important;
transition: opacity 240ms ease, transform 240ms ease;
// 滚动条样式
&::-webkit-scrollbar {
@@ -918,6 +936,19 @@
}
}
.loading-messages.loading-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
background: rgba(10, 10, 10, 0.28);
backdrop-filter: blur(6px);
transition: opacity 200ms ease;
z-index: 2;
}
.message-list * {
-webkit-app-region: no-drag !important;
}
@@ -1108,6 +1139,7 @@
font-size: 14px;
line-height: 1.6;
word-break: break-word;
white-space: pre-wrap;
}
// 表情包消息
@@ -1432,6 +1464,7 @@
.quoted-text {
color: var(--text-secondary);
white-space: pre-wrap;
}
}

View File

@@ -108,6 +108,7 @@ function ChatPage(_props: ChatPageProps) {
const messageListRef = useRef<HTMLDivElement>(null)
const searchInputRef = useRef<HTMLInputElement>(null)
const sidebarRef = useRef<HTMLDivElement>(null)
const initialRevealTimerRef = useRef<number | null>(null)
const [currentOffset, setCurrentOffset] = useState(0)
const [myAvatarUrl, setMyAvatarUrl] = useState<string | undefined>(undefined)
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
@@ -118,6 +119,7 @@ function ChatPage(_props: ChatPageProps) {
const [isLoadingDetail, setIsLoadingDetail] = useState(false)
const [highlightedMessageKeys, setHighlightedMessageKeys] = useState<string[]>([])
const [isRefreshingSessions, setIsRefreshingSessions] = useState(false)
const [hasInitialMessages, setHasInitialMessages] = useState(false)
const highlightedMessageSet = useMemo(() => new Set(highlightedMessageKeys), [highlightedMessageKeys])
@@ -126,6 +128,7 @@ function ChatPage(_props: ChatPageProps) {
const sessionMapRef = useRef<Map<string, ChatSession>>(new Map())
const sessionsRef = useRef<ChatSession[]>([])
const currentSessionRef = useRef<string | null>(null)
const prevSessionRef = useRef<string | null>(null)
const isLoadingMessagesRef = useRef(false)
const isLoadingMoreRef = useRef(false)
const isConnectedRef = useRef(false)
@@ -511,6 +514,53 @@ function ChatPage(_props: ChatPageProps) {
isLoadingMoreRef.current = isLoadingMore
}, [isLoadingMessages, isLoadingMore])
useEffect(() => {
if (initialRevealTimerRef.current !== null) {
window.clearTimeout(initialRevealTimerRef.current)
initialRevealTimerRef.current = null
}
if (!isLoadingMessages) {
if (messages.length === 0) {
setHasInitialMessages(true)
} else {
initialRevealTimerRef.current = window.setTimeout(() => {
setHasInitialMessages(true)
initialRevealTimerRef.current = null
}, 120)
}
}
}, [isLoadingMessages, messages.length])
useEffect(() => {
if (currentSessionId !== prevSessionRef.current) {
prevSessionRef.current = currentSessionId
if (initialRevealTimerRef.current !== null) {
window.clearTimeout(initialRevealTimerRef.current)
initialRevealTimerRef.current = null
}
if (messages.length === 0) {
setHasInitialMessages(false)
} else if (!isLoadingMessages) {
setHasInitialMessages(true)
}
}
}, [currentSessionId, messages.length, isLoadingMessages])
useEffect(() => {
if (currentSessionId && messages.length === 0 && !isLoadingMessages && !isLoadingMore) {
loadMessages(currentSessionId, 0)
}
}, [currentSessionId, messages.length, isLoadingMessages, isLoadingMore])
useEffect(() => {
return () => {
if (initialRevealTimerRef.current !== null) {
window.clearTimeout(initialRevealTimerRef.current)
initialRevealTimerRef.current = null
}
}
}, [])
useEffect(() => {
isConnectedRef.current = isConnected
}, [isConnected])
@@ -710,18 +760,18 @@ function ChatPage(_props: ChatPageProps) {
</div>
</div>
<div className="message-content-wrapper">
{isLoadingMessages ? (
<div className="loading-messages">
<div className={`message-content-wrapper ${hasInitialMessages ? 'loaded' : 'loading'}`}>
{isLoadingMessages && !hasInitialMessages && (
<div className="loading-messages loading-overlay">
<Loader2 size={24} />
<span>...</span>
</div>
) : (
<div
className="message-list"
ref={messageListRef}
onScroll={handleScroll}
>
)}
<div
className={`message-list ${hasInitialMessages ? 'loaded' : 'loading'}`}
ref={messageListRef}
onScroll={handleScroll}
>
{hasMoreMessages && (
<div className={`load-more-trigger ${isLoadingMore ? 'loading' : ''}`}>
{isLoadingMore ? (
@@ -772,7 +822,6 @@ function ChatPage(_props: ChatPageProps) {
<span></span>
</div>
</div>
)}
{/* 会话详情面板 */}
{showDetailPanel && (