mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat: 优化会话加载速度;优化动画表现;支持中文数据库路径
This commit is contained in:
@@ -49,6 +49,8 @@ export class WcdbService {
|
|||||||
private wcdbGetEmoticonCdnUrl: any = null
|
private wcdbGetEmoticonCdnUrl: any = null
|
||||||
private avatarUrlCache: Map<string, { url?: string; updatedAt: number }> = new Map()
|
private avatarUrlCache: Map<string, { url?: string; updatedAt: number }> = new Map()
|
||||||
private readonly avatarCacheTtlMs = 10 * 60 * 1000
|
private readonly avatarCacheTtlMs = 10 * 60 * 1000
|
||||||
|
private logTimer: NodeJS.Timeout | null = null
|
||||||
|
private lastLogTail: string | null = null
|
||||||
|
|
||||||
setPaths(resourcesPath: string, userDataPath: string): void {
|
setPaths(resourcesPath: string, userDataPath: string): void {
|
||||||
this.resourcesPath = resourcesPath
|
this.resourcesPath = resourcesPath
|
||||||
@@ -57,6 +59,11 @@ export class WcdbService {
|
|||||||
|
|
||||||
setLogEnabled(enabled: boolean): void {
|
setLogEnabled(enabled: boolean): void {
|
||||||
this.logEnabled = enabled
|
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 {
|
private decodeJsonPtr(outPtr: any): string | null {
|
||||||
if (!outPtr) return null
|
if (!outPtr) return null
|
||||||
try {
|
try {
|
||||||
@@ -545,6 +595,9 @@ export class WcdbService {
|
|||||||
console.warn('设置 wxid 失败:', e)
|
console.warn('设置 wxid 失败:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.isLogEnabled()) {
|
||||||
|
this.startLogPolling()
|
||||||
|
}
|
||||||
this.writeLog(`open ok handle=${handle}`)
|
this.writeLog(`open ok handle=${handle}`)
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -571,6 +624,7 @@ export class WcdbService {
|
|||||||
this.currentKey = null
|
this.currentKey = null
|
||||||
this.currentWxid = null
|
this.currentWxid = null
|
||||||
this.initialized = false
|
this.initialized = false
|
||||||
|
this.stopLogPolling()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "weflow",
|
"name": "weflow",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"description": "WeFlow - 微信聊天记录查看工具",
|
"description": "WeFlow - 微信聊天记录查看工具",
|
||||||
"main": "dist-electron/main.js",
|
"main": "dist-electron/main.js",
|
||||||
"author": "cc",
|
"author": "cc",
|
||||||
|
|||||||
Binary file not shown.
@@ -883,6 +883,23 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-app-region: no-drag;
|
-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 {
|
.message-list {
|
||||||
@@ -898,6 +915,7 @@
|
|||||||
background-color: var(--bg-tertiary);
|
background-color: var(--bg-tertiary);
|
||||||
position: relative;
|
position: relative;
|
||||||
-webkit-app-region: no-drag !important;
|
-webkit-app-region: no-drag !important;
|
||||||
|
transition: opacity 240ms ease, transform 240ms ease;
|
||||||
|
|
||||||
// 滚动条样式
|
// 滚动条样式
|
||||||
&::-webkit-scrollbar {
|
&::-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 * {
|
.message-list * {
|
||||||
-webkit-app-region: no-drag !important;
|
-webkit-app-region: no-drag !important;
|
||||||
}
|
}
|
||||||
@@ -1108,6 +1139,7 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表情包消息
|
// 表情包消息
|
||||||
@@ -1432,6 +1464,7 @@
|
|||||||
|
|
||||||
.quoted-text {
|
.quoted-text {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
const messageListRef = useRef<HTMLDivElement>(null)
|
const messageListRef = useRef<HTMLDivElement>(null)
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null)
|
const searchInputRef = useRef<HTMLInputElement>(null)
|
||||||
const sidebarRef = useRef<HTMLDivElement>(null)
|
const sidebarRef = useRef<HTMLDivElement>(null)
|
||||||
|
const initialRevealTimerRef = useRef<number | null>(null)
|
||||||
const [currentOffset, setCurrentOffset] = useState(0)
|
const [currentOffset, setCurrentOffset] = useState(0)
|
||||||
const [myAvatarUrl, setMyAvatarUrl] = useState<string | undefined>(undefined)
|
const [myAvatarUrl, setMyAvatarUrl] = useState<string | undefined>(undefined)
|
||||||
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
|
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
|
||||||
@@ -118,6 +119,7 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
const [isLoadingDetail, setIsLoadingDetail] = useState(false)
|
const [isLoadingDetail, setIsLoadingDetail] = useState(false)
|
||||||
const [highlightedMessageKeys, setHighlightedMessageKeys] = useState<string[]>([])
|
const [highlightedMessageKeys, setHighlightedMessageKeys] = useState<string[]>([])
|
||||||
const [isRefreshingSessions, setIsRefreshingSessions] = useState(false)
|
const [isRefreshingSessions, setIsRefreshingSessions] = useState(false)
|
||||||
|
const [hasInitialMessages, setHasInitialMessages] = useState(false)
|
||||||
|
|
||||||
|
|
||||||
const highlightedMessageSet = useMemo(() => new Set(highlightedMessageKeys), [highlightedMessageKeys])
|
const highlightedMessageSet = useMemo(() => new Set(highlightedMessageKeys), [highlightedMessageKeys])
|
||||||
@@ -126,6 +128,7 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
const sessionMapRef = useRef<Map<string, ChatSession>>(new Map())
|
const sessionMapRef = useRef<Map<string, ChatSession>>(new Map())
|
||||||
const sessionsRef = useRef<ChatSession[]>([])
|
const sessionsRef = useRef<ChatSession[]>([])
|
||||||
const currentSessionRef = useRef<string | null>(null)
|
const currentSessionRef = useRef<string | null>(null)
|
||||||
|
const prevSessionRef = useRef<string | null>(null)
|
||||||
const isLoadingMessagesRef = useRef(false)
|
const isLoadingMessagesRef = useRef(false)
|
||||||
const isLoadingMoreRef = useRef(false)
|
const isLoadingMoreRef = useRef(false)
|
||||||
const isConnectedRef = useRef(false)
|
const isConnectedRef = useRef(false)
|
||||||
@@ -511,6 +514,53 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
isLoadingMoreRef.current = isLoadingMore
|
isLoadingMoreRef.current = isLoadingMore
|
||||||
}, [isLoadingMessages, 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(() => {
|
useEffect(() => {
|
||||||
isConnectedRef.current = isConnected
|
isConnectedRef.current = isConnected
|
||||||
}, [isConnected])
|
}, [isConnected])
|
||||||
@@ -710,15 +760,15 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="message-content-wrapper">
|
<div className={`message-content-wrapper ${hasInitialMessages ? 'loaded' : 'loading'}`}>
|
||||||
{isLoadingMessages ? (
|
{isLoadingMessages && !hasInitialMessages && (
|
||||||
<div className="loading-messages">
|
<div className="loading-messages loading-overlay">
|
||||||
<Loader2 size={24} />
|
<Loader2 size={24} />
|
||||||
<span>加载消息中...</span>
|
<span>加载消息中...</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
)}
|
||||||
<div
|
<div
|
||||||
className="message-list"
|
className={`message-list ${hasInitialMessages ? 'loaded' : 'loading'}`}
|
||||||
ref={messageListRef}
|
ref={messageListRef}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
@@ -772,7 +822,6 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
<span>回到底部</span>
|
<span>回到底部</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 会话详情面板 */}
|
{/* 会话详情面板 */}
|
||||||
{showDetailPanel && (
|
{showDetailPanel && (
|
||||||
|
|||||||
Reference in New Issue
Block a user