fix(export,sns): preserve sns load state across route switches

This commit is contained in:
aits2026
2026-03-05 19:35:42 +08:00
parent 2140a220e2
commit c625756ab4
2 changed files with 88 additions and 13 deletions

View File

@@ -1977,8 +1977,40 @@ function ExportPage() {
return return
} }
patchSessionLoadTraceStage(targetSessionIds, 'snsPostCounts', 'pending', { force: true }) const scopeKey = exportCacheScopeReadyRef.current
patchSessionLoadTraceStage(targetSessionIds, 'snsPostCounts', 'loading') ? exportCacheScopeRef.current
: await ensureExportCacheScope()
const targetSet = new Set(targetSessionIds)
let cachedCounts: Record<string, number> = {}
try {
const cached = await configService.getExportSnsUserPostCountsCache(scopeKey)
cachedCounts = cached?.counts || {}
} catch (cacheError) {
console.error('读取导出页朋友圈条数缓存失败:', cacheError)
}
const cachedTargetCounts = Object.entries(cachedCounts).reduce<Record<string, number>>((acc, [sessionId, countRaw]) => {
if (!targetSet.has(sessionId)) return acc
const nextCount = Number(countRaw)
acc[sessionId] = Number.isFinite(nextCount) ? Math.max(0, Math.floor(nextCount)) : 0
return acc
}, {})
const cachedReadySessionIds = Object.keys(cachedTargetCounts)
if (cachedReadySessionIds.length > 0) {
setSnsUserPostCounts(prev => ({ ...prev, ...cachedTargetCounts }))
patchSessionLoadTraceStage(cachedReadySessionIds, 'snsPostCounts', 'done')
}
const pendingSessionIds = options?.force
? targetSessionIds
: targetSessionIds.filter((sessionId) => !(sessionId in cachedTargetCounts))
if (pendingSessionIds.length === 0) {
setSnsUserPostCountsStatus('ready')
return
}
patchSessionLoadTraceStage(pendingSessionIds, 'snsPostCounts', 'pending', { force: true })
patchSessionLoadTraceStage(pendingSessionIds, 'snsPostCounts', 'loading')
setSnsUserPostCountsStatus('loading') setSnsUserPostCountsStatus('loading')
let normalizedCounts: Record<string, number> = {} let normalizedCounts: Record<string, number> = {}
@@ -1987,7 +2019,7 @@ function ExportPage() {
if (runToken !== snsUserPostCountsHydrationTokenRef.current) return if (runToken !== snsUserPostCountsHydrationTokenRef.current) return
if (!result.success || !result.counts) { if (!result.success || !result.counts) {
patchSessionLoadTraceStage(targetSessionIds, 'snsPostCounts', 'failed', { patchSessionLoadTraceStage(pendingSessionIds, 'snsPostCounts', 'failed', {
error: result.error || '朋友圈条数统计失败' error: result.error || '朋友圈条数统计失败'
}) })
setSnsUserPostCountsStatus('error') setSnsUserPostCountsStatus('error')
@@ -2003,9 +2035,6 @@ function ExportPage() {
void (async () => { void (async () => {
try { try {
const scopeKey = exportCacheScopeReadyRef.current
? exportCacheScopeRef.current
: await ensureExportCacheScope()
await configService.setExportSnsUserPostCountsCache(scopeKey, normalizedCounts) await configService.setExportSnsUserPostCountsCache(scopeKey, normalizedCounts)
} catch (cacheError) { } catch (cacheError) {
console.error('写入导出页朋友圈条数缓存失败:', cacheError) console.error('写入导出页朋友圈条数缓存失败:', cacheError)
@@ -2014,7 +2043,7 @@ function ExportPage() {
} catch (error) { } catch (error) {
console.error('加载朋友圈用户条数失败:', error) console.error('加载朋友圈用户条数失败:', error)
if (runToken !== snsUserPostCountsHydrationTokenRef.current) return if (runToken !== snsUserPostCountsHydrationTokenRef.current) return
patchSessionLoadTraceStage(targetSessionIds, 'snsPostCounts', 'failed', { patchSessionLoadTraceStage(pendingSessionIds, 'snsPostCounts', 'failed', {
error: String(error) error: String(error)
}) })
setSnsUserPostCountsStatus('error') setSnsUserPostCountsStatus('error')
@@ -2025,7 +2054,7 @@ function ExportPage() {
const applyBatch = () => { const applyBatch = () => {
if (runToken !== snsUserPostCountsHydrationTokenRef.current) return if (runToken !== snsUserPostCountsHydrationTokenRef.current) return
const batchSessionIds = targetSessionIds.slice(cursor, cursor + SNS_USER_POST_COUNT_BATCH_SIZE) const batchSessionIds = pendingSessionIds.slice(cursor, cursor + SNS_USER_POST_COUNT_BATCH_SIZE)
if (batchSessionIds.length === 0) { if (batchSessionIds.length === 0) {
setSnsUserPostCountsStatus('ready') setSnsUserPostCountsStatus('ready')
snsUserPostCountsBatchTimerRef.current = null snsUserPostCountsBatchTimerRef.current = null
@@ -2984,7 +3013,7 @@ function ExportPage() {
} }
setIsSessionEnriching(false) setIsSessionEnriching(false)
setIsLoadingSessionCounts(false) setIsLoadingSessionCounts(false)
setSnsUserPostCountsStatus('idle') setSnsUserPostCountsStatus(prev => (prev === 'loading' ? 'idle' : prev))
}, [isExportRoute]) }, [isExportRoute])
useEffect(() => { useEffect(() => {

View File

@@ -504,7 +504,10 @@ export default function SnsPage() {
} }
}, []) }, [])
const hydrateContactPostCounts = useCallback(async (usernames: string[], options?: { force?: boolean }) => { const hydrateContactPostCounts = useCallback(async (
usernames: string[],
options?: { force?: boolean; readyUsernames?: Set<string> }
) => {
const force = options?.force === true const force = options?.force === true
const targets = usernames const targets = usernames
.map((username) => String(username || '').trim()) .map((username) => String(username || '').trim())
@@ -512,7 +515,7 @@ export default function SnsPage() {
stopContactsCountHydration(true) stopContactsCountHydration(true)
if (targets.length === 0) return if (targets.length === 0) return
const readySet = new Set( const readySet = options?.readyUsernames || new Set(
contactsRef.current contactsRef.current
.filter((contact) => contact.postCountStatus === 'ready' && typeof contact.postCount === 'number') .filter((contact) => contact.postCountStatus === 'ready' && typeof contact.postCount === 'number')
.map((contact) => contact.username) .map((contact) => contact.username)
@@ -625,8 +628,42 @@ export default function SnsPage() {
setContactsLoading(true) setContactsLoading(true)
try { try {
const snsPostCountsScopeKey = await ensureSnsUserPostCountsCacheScopeKey() const snsPostCountsScopeKey = await ensureSnsUserPostCountsCacheScopeKey()
const cachedPostCountsItem = await configService.getExportSnsUserPostCountsCache(snsPostCountsScopeKey) const [cachedPostCountsItem, cachedContactsItem, cachedAvatarItem] = await Promise.all([
configService.getExportSnsUserPostCountsCache(snsPostCountsScopeKey),
configService.getContactsListCache(snsPostCountsScopeKey),
configService.getContactsAvatarCache(snsPostCountsScopeKey)
])
const cachedPostCounts = cachedPostCountsItem?.counts || {} const cachedPostCounts = cachedPostCountsItem?.counts || {}
const cachedAvatarMap = cachedAvatarItem?.avatars || {}
const cachedContacts = (cachedContactsItem?.contacts || [])
.filter((contact) => contact.type === 'friend' || contact.type === 'former_friend')
.map((contact) => {
const cachedCount = cachedPostCounts[contact.username]
const hasCachedCount = typeof cachedCount === 'number' && Number.isFinite(cachedCount)
return {
username: contact.username,
displayName: contact.displayName || contact.username,
avatarUrl: cachedAvatarMap[contact.username]?.avatarUrl,
type: (contact.type === 'former_friend' ? 'former_friend' : 'friend') as 'friend' | 'former_friend',
lastSessionTimestamp: 0,
postCount: hasCachedCount ? Math.max(0, Math.floor(cachedCount)) : undefined,
postCountStatus: hasCachedCount ? 'ready' as ContactPostCountStatus : 'idle' as ContactPostCountStatus
}
})
if (requestToken !== contactsLoadTokenRef.current) return
if (cachedContacts.length > 0) {
const cachedContactsSorted = sortContactsForRanking(cachedContacts)
setContacts(cachedContactsSorted)
setContactsLoading(false)
const cachedReadyCount = cachedContactsSorted.filter(contact => contact.postCountStatus === 'ready').length
setContactsCountProgress({
resolved: cachedReadyCount,
total: cachedContactsSorted.length,
running: cachedReadyCount < cachedContactsSorted.length
})
}
const [contactsResult, sessionsResult] = await Promise.all([ const [contactsResult, sessionsResult] = await Promise.all([
window.electronAPI.chat.getContacts(), window.electronAPI.chat.getContacts(),
window.electronAPI.chat.getSessions() window.electronAPI.chat.getSessions()
@@ -670,7 +707,15 @@ export default function SnsPage() {
let contactsList = sortContactsForRanking(Array.from(contactMap.values())) let contactsList = sortContactsForRanking(Array.from(contactMap.values()))
if (requestToken !== contactsLoadTokenRef.current) return if (requestToken !== contactsLoadTokenRef.current) return
setContacts(contactsList) setContacts(contactsList)
void hydrateContactPostCounts(contactsList.map(contact => contact.username)) const readyUsernames = new Set(
contactsList
.filter((contact) => contact.postCountStatus === 'ready' && typeof contact.postCount === 'number')
.map((contact) => contact.username)
)
void hydrateContactPostCounts(
contactsList.map(contact => contact.username),
{ readyUsernames }
)
const allUsernames = contactsList.map(c => c.username) const allUsernames = contactsList.map(c => c.username)
@@ -880,6 +925,7 @@ export default function SnsPage() {
useEffect(() => { useEffect(() => {
const handleChange = () => { const handleChange = () => {
cacheScopeKeyRef.current = '' cacheScopeKeyRef.current = ''
snsUserPostCountsCacheScopeKeyRef.current = ''
// wxid changed, reset everything // wxid changed, reset everything
stopContactsCountHydration(true) stopContactsCountHydration(true)
setContacts([]) setContacts([])