mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 15:25:50 +00:00
fix(export,sns): share sns user count cache across pages
This commit is contained in:
@@ -2000,6 +2000,17 @@ function ExportPage() {
|
|||||||
const value = Number(rawCount)
|
const value = Number(rawCount)
|
||||||
normalizedCounts[username] = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0
|
normalizedCounts[username] = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const scopeKey = exportCacheScopeReadyRef.current
|
||||||
|
? exportCacheScopeRef.current
|
||||||
|
: await ensureExportCacheScope()
|
||||||
|
await configService.setExportSnsUserPostCountsCache(scopeKey, normalizedCounts)
|
||||||
|
} catch (cacheError) {
|
||||||
|
console.error('写入导出页朋友圈条数缓存失败:', cacheError)
|
||||||
|
}
|
||||||
|
})()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载朋友圈用户条数失败:', error)
|
console.error('加载朋友圈用户条数失败:', error)
|
||||||
if (runToken !== snsUserPostCountsHydrationTokenRef.current) return
|
if (runToken !== snsUserPostCountsHydrationTokenRef.current) return
|
||||||
@@ -2040,7 +2051,7 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyBatch()
|
applyBatch()
|
||||||
}, [patchSessionLoadTraceStage, snsUserPostCountsStatus])
|
}, [ensureExportCacheScope, patchSessionLoadTraceStage, snsUserPostCountsStatus])
|
||||||
|
|
||||||
const loadSessionSnsTimelinePosts = useCallback(async (target: SessionSnsTimelineTarget, options?: { reset?: boolean }) => {
|
const loadSessionSnsTimelinePosts = useCallback(async (target: SessionSnsTimelineTarget, options?: { reset?: boolean }) => {
|
||||||
const reset = Boolean(options?.reset)
|
const reset = Boolean(options?.reset)
|
||||||
|
|||||||
@@ -113,12 +113,14 @@ export default function SnsPage() {
|
|||||||
const [hasNewer, setHasNewer] = useState(false)
|
const [hasNewer, setHasNewer] = useState(false)
|
||||||
const [loadingNewer, setLoadingNewer] = useState(false)
|
const [loadingNewer, setLoadingNewer] = useState(false)
|
||||||
const postsRef = useRef<SnsPost[]>([])
|
const postsRef = useRef<SnsPost[]>([])
|
||||||
|
const contactsRef = useRef<Contact[]>([])
|
||||||
const overviewStatsRef = useRef<SnsOverviewStats>(overviewStats)
|
const overviewStatsRef = useRef<SnsOverviewStats>(overviewStats)
|
||||||
const overviewStatsStatusRef = useRef<OverviewStatsStatus>(overviewStatsStatus)
|
const overviewStatsStatusRef = useRef<OverviewStatsStatus>(overviewStatsStatus)
|
||||||
const selectedUsernamesRef = useRef<string[]>(selectedUsernames)
|
const selectedUsernamesRef = useRef<string[]>(selectedUsernames)
|
||||||
const searchKeywordRef = useRef(searchKeyword)
|
const searchKeywordRef = useRef(searchKeyword)
|
||||||
const jumpTargetDateRef = useRef<Date | undefined>(jumpTargetDate)
|
const jumpTargetDateRef = useRef<Date | undefined>(jumpTargetDate)
|
||||||
const cacheScopeKeyRef = useRef('')
|
const cacheScopeKeyRef = useRef('')
|
||||||
|
const snsUserPostCountsCacheScopeKeyRef = useRef('')
|
||||||
const scrollAdjustmentRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null)
|
const scrollAdjustmentRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null)
|
||||||
const contactsLoadTokenRef = useRef(0)
|
const contactsLoadTokenRef = useRef(0)
|
||||||
const contactsCountHydrationTokenRef = useRef(0)
|
const contactsCountHydrationTokenRef = useRef(0)
|
||||||
@@ -132,6 +134,9 @@ export default function SnsPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
postsRef.current = posts
|
postsRef.current = posts
|
||||||
}, [posts])
|
}, [posts])
|
||||||
|
useEffect(() => {
|
||||||
|
contactsRef.current = contacts
|
||||||
|
}, [contacts])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
overviewStatsRef.current = overviewStats
|
overviewStatsRef.current = overviewStats
|
||||||
}, [overviewStats])
|
}, [overviewStats])
|
||||||
@@ -222,6 +227,21 @@ export default function SnsPage() {
|
|||||||
return scopeKey
|
return scopeKey
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const ensureSnsUserPostCountsCacheScopeKey = useCallback(async () => {
|
||||||
|
if (snsUserPostCountsCacheScopeKeyRef.current) return snsUserPostCountsCacheScopeKeyRef.current
|
||||||
|
const [wxidRaw, dbPathRaw] = await Promise.all([
|
||||||
|
configService.getMyWxid(),
|
||||||
|
configService.getDbPath()
|
||||||
|
])
|
||||||
|
const wxid = String(wxidRaw || '').trim()
|
||||||
|
const dbPath = String(dbPathRaw || '').trim()
|
||||||
|
const scopeKey = (dbPath || wxid)
|
||||||
|
? `${dbPath}::${wxid}`
|
||||||
|
: 'default'
|
||||||
|
snsUserPostCountsCacheScopeKeyRef.current = scopeKey
|
||||||
|
return scopeKey
|
||||||
|
}, [])
|
||||||
|
|
||||||
const persistSnsPageCache = useCallback(async (patch?: { posts?: SnsPost[]; overviewStats?: SnsOverviewStats }) => {
|
const persistSnsPageCache = useCallback(async (patch?: { posts?: SnsPost[]; overviewStats?: SnsOverviewStats }) => {
|
||||||
if (!isDefaultViewNow()) return
|
if (!isDefaultViewNow()) return
|
||||||
try {
|
try {
|
||||||
@@ -484,17 +504,25 @@ export default function SnsPage() {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const hydrateContactPostCounts = useCallback(async (usernames: string[]) => {
|
const hydrateContactPostCounts = useCallback(async (usernames: string[], options?: { force?: boolean }) => {
|
||||||
|
const force = options?.force === true
|
||||||
const targets = usernames
|
const targets = usernames
|
||||||
.map((username) => String(username || '').trim())
|
.map((username) => String(username || '').trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
stopContactsCountHydration(true)
|
stopContactsCountHydration(true)
|
||||||
if (targets.length === 0) return
|
if (targets.length === 0) return
|
||||||
|
|
||||||
|
const readySet = new Set(
|
||||||
|
contactsRef.current
|
||||||
|
.filter((contact) => contact.postCountStatus === 'ready' && typeof contact.postCount === 'number')
|
||||||
|
.map((contact) => contact.username)
|
||||||
|
)
|
||||||
|
const pendingTargets = force ? targets : targets.filter((username) => !readySet.has(username))
|
||||||
const runToken = ++contactsCountHydrationTokenRef.current
|
const runToken = ++contactsCountHydrationTokenRef.current
|
||||||
const totalTargets = targets.length
|
const totalTargets = targets.length
|
||||||
const targetSet = new Set(targets)
|
const targetSet = new Set(pendingTargets)
|
||||||
|
|
||||||
|
if (pendingTargets.length > 0) {
|
||||||
setContacts((prev) => {
|
setContacts((prev) => {
|
||||||
let changed = false
|
let changed = false
|
||||||
const next = prev.map((contact) => {
|
const next = prev.map((contact) => {
|
||||||
@@ -503,17 +531,20 @@ export default function SnsPage() {
|
|||||||
changed = true
|
changed = true
|
||||||
return {
|
return {
|
||||||
...contact,
|
...contact,
|
||||||
postCount: undefined,
|
postCount: force ? undefined : contact.postCount,
|
||||||
postCountStatus: 'loading' as ContactPostCountStatus
|
postCountStatus: 'loading' as ContactPostCountStatus
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return changed ? sortContactsForRanking(next) : prev
|
return changed ? sortContactsForRanking(next) : prev
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
const preResolved = Math.max(0, totalTargets - pendingTargets.length)
|
||||||
setContactsCountProgress({
|
setContactsCountProgress({
|
||||||
resolved: 0,
|
resolved: preResolved,
|
||||||
total: totalTargets,
|
total: totalTargets,
|
||||||
running: true
|
running: pendingTargets.length > 0
|
||||||
})
|
})
|
||||||
|
if (pendingTargets.length === 0) return
|
||||||
|
|
||||||
let normalizedCounts: Record<string, number> = {}
|
let normalizedCounts: Record<string, number> = {}
|
||||||
try {
|
try {
|
||||||
@@ -523,17 +554,25 @@ export default function SnsPage() {
|
|||||||
normalizedCounts = Object.fromEntries(
|
normalizedCounts = Object.fromEntries(
|
||||||
Object.entries(result.counts).map(([username, value]) => [username, normalizePostCount(value)])
|
Object.entries(result.counts).map(([username, value]) => [username, normalizePostCount(value)])
|
||||||
)
|
)
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const scopeKey = await ensureSnsUserPostCountsCacheScopeKey()
|
||||||
|
await configService.setExportSnsUserPostCountsCache(scopeKey, normalizedCounts)
|
||||||
|
} catch (cacheError) {
|
||||||
|
console.error('Failed to persist SNS user post counts cache:', cacheError)
|
||||||
|
}
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load contact post counts:', error)
|
console.error('Failed to load contact post counts:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
let resolved = 0
|
let resolved = preResolved
|
||||||
let cursor = 0
|
let cursor = 0
|
||||||
const applyBatch = () => {
|
const applyBatch = () => {
|
||||||
if (runToken !== contactsCountHydrationTokenRef.current) return
|
if (runToken !== contactsCountHydrationTokenRef.current) return
|
||||||
|
|
||||||
const batch = targets.slice(cursor, cursor + CONTACT_COUNT_BATCH_SIZE)
|
const batch = pendingTargets.slice(cursor, cursor + CONTACT_COUNT_BATCH_SIZE)
|
||||||
if (batch.length === 0) {
|
if (batch.length === 0) {
|
||||||
setContactsCountProgress({
|
setContactsCountProgress({
|
||||||
resolved: totalTargets,
|
resolved: totalTargets,
|
||||||
@@ -585,6 +624,9 @@ export default function SnsPage() {
|
|||||||
stopContactsCountHydration(true)
|
stopContactsCountHydration(true)
|
||||||
setContactsLoading(true)
|
setContactsLoading(true)
|
||||||
try {
|
try {
|
||||||
|
const snsPostCountsScopeKey = await ensureSnsUserPostCountsCacheScopeKey()
|
||||||
|
const cachedPostCountsItem = await configService.getExportSnsUserPostCountsCache(snsPostCountsScopeKey)
|
||||||
|
const cachedPostCounts = cachedPostCountsItem?.counts || {}
|
||||||
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()
|
||||||
@@ -610,14 +652,16 @@ export default function SnsPage() {
|
|||||||
if (contactsResult.success && contactsResult.contacts) {
|
if (contactsResult.success && contactsResult.contacts) {
|
||||||
for (const c of contactsResult.contacts) {
|
for (const c of contactsResult.contacts) {
|
||||||
if (c.type === 'friend' || c.type === 'former_friend') {
|
if (c.type === 'friend' || c.type === 'former_friend') {
|
||||||
|
const cachedCount = cachedPostCounts[c.username]
|
||||||
|
const hasCachedCount = typeof cachedCount === 'number' && Number.isFinite(cachedCount)
|
||||||
contactMap.set(c.username, {
|
contactMap.set(c.username, {
|
||||||
username: c.username,
|
username: c.username,
|
||||||
displayName: c.displayName,
|
displayName: c.displayName,
|
||||||
avatarUrl: c.avatarUrl,
|
avatarUrl: c.avatarUrl,
|
||||||
type: c.type === 'former_friend' ? 'former_friend' : 'friend',
|
type: c.type === 'former_friend' ? 'former_friend' : 'friend',
|
||||||
lastSessionTimestamp: Number(sessionTimestampMap.get(c.username) || 0),
|
lastSessionTimestamp: Number(sessionTimestampMap.get(c.username) || 0),
|
||||||
postCount: undefined,
|
postCount: hasCachedCount ? Math.max(0, Math.floor(cachedCount)) : undefined,
|
||||||
postCountStatus: 'idle'
|
postCountStatus: hasCachedCount ? 'ready' : 'idle'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -668,7 +712,7 @@ export default function SnsPage() {
|
|||||||
setContactsLoading(false)
|
setContactsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [hydrateContactPostCounts, sortContactsForRanking, stopContactsCountHydration])
|
}, [ensureSnsUserPostCountsCacheScopeKey, hydrateContactPostCounts, sortContactsForRanking, stopContactsCountHydration])
|
||||||
|
|
||||||
const closeAuthorTimeline = useCallback(() => {
|
const closeAuthorTimeline = useCallback(() => {
|
||||||
authorTimelineRequestTokenRef.current += 1
|
authorTimelineRequestTokenRef.current += 1
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const CONFIG_KEYS = {
|
|||||||
EXPORT_SESSION_MESSAGE_COUNT_CACHE_MAP: 'exportSessionMessageCountCacheMap',
|
EXPORT_SESSION_MESSAGE_COUNT_CACHE_MAP: 'exportSessionMessageCountCacheMap',
|
||||||
EXPORT_SESSION_CONTENT_METRIC_CACHE_MAP: 'exportSessionContentMetricCacheMap',
|
EXPORT_SESSION_CONTENT_METRIC_CACHE_MAP: 'exportSessionContentMetricCacheMap',
|
||||||
EXPORT_SNS_STATS_CACHE_MAP: 'exportSnsStatsCacheMap',
|
EXPORT_SNS_STATS_CACHE_MAP: 'exportSnsStatsCacheMap',
|
||||||
|
EXPORT_SNS_USER_POST_COUNTS_CACHE_MAP: 'exportSnsUserPostCountsCacheMap',
|
||||||
SNS_PAGE_CACHE_MAP: 'snsPageCacheMap',
|
SNS_PAGE_CACHE_MAP: 'snsPageCacheMap',
|
||||||
CONTACTS_LOAD_TIMEOUT_MS: 'contactsLoadTimeoutMs',
|
CONTACTS_LOAD_TIMEOUT_MS: 'contactsLoadTimeoutMs',
|
||||||
CONTACTS_LIST_CACHE_MAP: 'contactsListCacheMap',
|
CONTACTS_LIST_CACHE_MAP: 'contactsListCacheMap',
|
||||||
@@ -533,6 +534,11 @@ export interface ExportSnsStatsCacheItem {
|
|||||||
totalFriends: number
|
totalFriends: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExportSnsUserPostCountsCacheItem {
|
||||||
|
updatedAt: number
|
||||||
|
counts: Record<string, number>
|
||||||
|
}
|
||||||
|
|
||||||
export interface SnsPageOverviewCache {
|
export interface SnsPageOverviewCache {
|
||||||
totalPosts: number
|
totalPosts: number
|
||||||
totalFriends: number
|
totalFriends: number
|
||||||
@@ -740,6 +746,58 @@ export async function setExportSnsStatsCache(
|
|||||||
await config.set(CONFIG_KEYS.EXPORT_SNS_STATS_CACHE_MAP, map)
|
await config.set(CONFIG_KEYS.EXPORT_SNS_STATS_CACHE_MAP, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getExportSnsUserPostCountsCache(scopeKey: string): Promise<ExportSnsUserPostCountsCacheItem | null> {
|
||||||
|
if (!scopeKey) return null
|
||||||
|
const value = await config.get(CONFIG_KEYS.EXPORT_SNS_USER_POST_COUNTS_CACHE_MAP)
|
||||||
|
if (!value || typeof value !== 'object') return null
|
||||||
|
const rawMap = value as Record<string, unknown>
|
||||||
|
const rawItem = rawMap[scopeKey]
|
||||||
|
if (!rawItem || typeof rawItem !== 'object') return null
|
||||||
|
|
||||||
|
const raw = rawItem as Record<string, unknown>
|
||||||
|
const rawCounts = raw.counts
|
||||||
|
if (!rawCounts || typeof rawCounts !== 'object') return null
|
||||||
|
|
||||||
|
const counts: Record<string, number> = {}
|
||||||
|
for (const [rawUsername, rawCount] of Object.entries(rawCounts as Record<string, unknown>)) {
|
||||||
|
const username = String(rawUsername || '').trim()
|
||||||
|
if (!username) continue
|
||||||
|
const valueNum = Number(rawCount)
|
||||||
|
counts[username] = Number.isFinite(valueNum) ? Math.max(0, Math.floor(valueNum)) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedAt = typeof raw.updatedAt === 'number' && Number.isFinite(raw.updatedAt)
|
||||||
|
? raw.updatedAt
|
||||||
|
: 0
|
||||||
|
return { updatedAt, counts }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setExportSnsUserPostCountsCache(
|
||||||
|
scopeKey: string,
|
||||||
|
counts: Record<string, number>
|
||||||
|
): Promise<void> {
|
||||||
|
if (!scopeKey) return
|
||||||
|
const current = await config.get(CONFIG_KEYS.EXPORT_SNS_USER_POST_COUNTS_CACHE_MAP)
|
||||||
|
const map = current && typeof current === 'object'
|
||||||
|
? { ...(current as Record<string, unknown>) }
|
||||||
|
: {}
|
||||||
|
|
||||||
|
const normalized: Record<string, number> = {}
|
||||||
|
for (const [rawUsername, rawCount] of Object.entries(counts || {})) {
|
||||||
|
const username = String(rawUsername || '').trim()
|
||||||
|
if (!username) continue
|
||||||
|
const valueNum = Number(rawCount)
|
||||||
|
normalized[username] = Number.isFinite(valueNum) ? Math.max(0, Math.floor(valueNum)) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
map[scopeKey] = {
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
counts: normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
await config.set(CONFIG_KEYS.EXPORT_SNS_USER_POST_COUNTS_CACHE_MAP, map)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSnsPageCache(scopeKey: string): Promise<SnsPageCacheItem | null> {
|
export async function getSnsPageCache(scopeKey: string): Promise<SnsPageCacheItem | null> {
|
||||||
if (!scopeKey) return null
|
if (!scopeKey) return null
|
||||||
const value = await config.get(CONFIG_KEYS.SNS_PAGE_CACHE_MAP)
|
const value = await config.get(CONFIG_KEYS.SNS_PAGE_CACHE_MAP)
|
||||||
|
|||||||
Reference in New Issue
Block a user