diff --git a/electron/services/snsService.ts b/electron/services/snsService.ts index b43645f..8cb56f6 100644 --- a/electron/services/snsService.ts +++ b/electron/services/snsService.ts @@ -291,7 +291,7 @@ class SnsService { private configService: ConfigService private contactCache: ContactCacheService private imageCache = new Map() - private exportStatsCache: { totalPosts: number; totalFriends: number; updatedAt: number } | null = null + private exportStatsCache: { totalPosts: number; totalFriends: number; myPosts: number | null; updatedAt: number } | null = null private readonly exportStatsCacheTtlMs = 5 * 60 * 1000 private lastTimelineFallbackAt = 0 private readonly timelineFallbackCooldownMs = 3 * 60 * 1000 @@ -512,11 +512,13 @@ class SnsService { return raw.trim() } - private async getExportStatsFromTimeline(): Promise<{ totalPosts: number; totalFriends: number }> { + private async getExportStatsFromTimeline(myWxid?: string): Promise<{ totalPosts: number; totalFriends: number; myPosts: number | null }> { const pageSize = 500 const uniqueUsers = new Set() let totalPosts = 0 + let myPosts = 0 let offset = 0 + const normalizedMyWxid = this.toOptionalString(myWxid) for (let round = 0; round < 2000; round++) { const result = await wcdbService.getSnsTimeline(pageSize, offset, undefined, undefined, 0, 0) @@ -531,6 +533,7 @@ class SnsService { for (const row of rows) { const username = this.pickTimelineUsername(row) if (username) uniqueUsers.add(username) + if (normalizedMyWxid && username === normalizedMyWxid) myPosts += 1 } if (rows.length < pageSize) break @@ -539,7 +542,8 @@ class SnsService { return { totalPosts, - totalFriends: uniqueUsers.size + totalFriends: uniqueUsers.size, + myPosts: normalizedMyWxid ? myPosts : null } } @@ -735,9 +739,10 @@ class SnsService { } } - private async getExportStatsFromTableCount(): Promise<{ totalPosts: number; totalFriends: number }> { + private async getExportStatsFromTableCount(myWxid?: string): Promise<{ totalPosts: number; totalFriends: number; myPosts: number | null }> { let totalPosts = 0 let totalFriends = 0 + let myPosts: number | null = null const postCountResult = await wcdbService.execQuery('sns', null, 'SELECT COUNT(1) AS total FROM SnsTimeLine') if (postCountResult.success && postCountResult.rows && postCountResult.rows.length > 0) { @@ -764,16 +769,40 @@ class SnsService { } } - return { totalPosts, totalFriends } + const normalizedMyWxid = this.toOptionalString(myWxid) + if (normalizedMyWxid) { + const myPostPrimary = await wcdbService.execQuery( + 'sns', + null, + "SELECT COUNT(1) AS total FROM SnsTimeLine WHERE user_name = ?", + [normalizedMyWxid] + ) + if (myPostPrimary.success && myPostPrimary.rows && myPostPrimary.rows.length > 0) { + myPosts = this.parseCountValue(myPostPrimary.rows[0]) + } else { + const myPostFallback = await wcdbService.execQuery( + 'sns', + null, + "SELECT COUNT(1) AS total FROM SnsTimeLine WHERE userName = ?", + [normalizedMyWxid] + ) + if (myPostFallback.success && myPostFallback.rows && myPostFallback.rows.length > 0) { + myPosts = this.parseCountValue(myPostFallback.rows[0]) + } + } + } + + return { totalPosts, totalFriends, myPosts } } async getExportStats(options?: { allowTimelineFallback?: boolean preferCache?: boolean - }): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number }; error?: string }> { + }): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number; myPosts: number | null }; error?: string }> { const allowTimelineFallback = options?.allowTimelineFallback ?? true const preferCache = options?.preferCache ?? false const now = Date.now() + const myWxid = this.toOptionalString(this.configService.get('myWxid')) try { if (preferCache && this.exportStatsCache && now - this.exportStatsCache.updatedAt <= this.exportStatsCacheTtlMs) { @@ -781,12 +810,13 @@ class SnsService { success: true, data: { totalPosts: this.exportStatsCache.totalPosts, - totalFriends: this.exportStatsCache.totalFriends + totalFriends: this.exportStatsCache.totalFriends, + myPosts: this.exportStatsCache.myPosts } } } - let { totalPosts, totalFriends } = await this.getExportStatsFromTableCount() + let { totalPosts, totalFriends, myPosts } = await this.getExportStatsFromTableCount(myWxid) let fallbackAttempted = false let fallbackError = '' @@ -798,7 +828,7 @@ class SnsService { ) { fallbackAttempted = true try { - const timelineStats = await this.getExportStatsFromTimeline() + const timelineStats = await this.getExportStatsFromTimeline(myWxid) this.lastTimelineFallbackAt = Date.now() if (timelineStats.totalPosts > 0) { totalPosts = timelineStats.totalPosts @@ -806,6 +836,9 @@ class SnsService { if (timelineStats.totalFriends > 0) { totalFriends = timelineStats.totalFriends } + if (timelineStats.myPosts !== null) { + myPosts = timelineStats.myPosts + } } catch (error) { fallbackError = String(error) console.error('[SnsService] getExportStats timeline fallback failed:', error) @@ -814,7 +847,10 @@ class SnsService { const normalizedStats = { totalPosts: Math.max(0, Number(totalPosts || 0)), - totalFriends: Math.max(0, Number(totalFriends || 0)) + totalFriends: Math.max(0, Number(totalFriends || 0)), + myPosts: myWxid + ? (myPosts === null ? null : Math.max(0, Number(myPosts || 0))) + : null } const computedHasData = normalizedStats.totalPosts > 0 || normalizedStats.totalFriends > 0 const cacheHasData = !!this.exportStatsCache && (this.exportStatsCache.totalPosts > 0 || this.exportStatsCache.totalFriends > 0) @@ -825,7 +861,8 @@ class SnsService { success: true, data: { totalPosts: this.exportStatsCache.totalPosts, - totalFriends: this.exportStatsCache.totalFriends + totalFriends: this.exportStatsCache.totalFriends, + myPosts: this.exportStatsCache.myPosts } } } @@ -838,6 +875,7 @@ class SnsService { this.exportStatsCache = { totalPosts: normalizedStats.totalPosts, totalFriends: normalizedStats.totalFriends, + myPosts: normalizedStats.myPosts, updatedAt: Date.now() } @@ -848,7 +886,8 @@ class SnsService { success: true, data: { totalPosts: this.exportStatsCache.totalPosts, - totalFriends: this.exportStatsCache.totalFriends + totalFriends: this.exportStatsCache.totalFriends, + myPosts: this.exportStatsCache.myPosts } } } @@ -856,7 +895,7 @@ class SnsService { } } - async getExportStatsFast(): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number }; error?: string }> { + async getExportStatsFast(): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number; myPosts: number | null }; error?: string }> { return this.getExportStats({ allowTimelineFallback: false, preferCache: true diff --git a/src/pages/SnsPage.tsx b/src/pages/SnsPage.tsx index 4da076f..a9ae083 100644 --- a/src/pages/SnsPage.tsx +++ b/src/pages/SnsPage.tsx @@ -25,6 +25,7 @@ interface Contact { interface SnsOverviewStats { totalPosts: number totalFriends: number + myPosts: number | null earliestTime: number | null latestTime: number | null } @@ -39,6 +40,7 @@ export default function SnsPage() { const [overviewStats, setOverviewStats] = useState({ totalPosts: 0, totalFriends: 0, + myPosts: null, earliestTime: null, latestTime: null }) @@ -196,6 +198,9 @@ export default function SnsPage() { setOverviewStats({ totalPosts: cachedTotalPosts, totalFriends: cachedTotalFriends, + myPosts: typeof cachedOverview.myPosts === 'number' && Number.isFinite(cachedOverview.myPosts) && cachedOverview.myPosts >= 0 + ? Math.floor(cachedOverview.myPosts) + : null, earliestTime: cachedOverview.earliestTime ?? null, latestTime: cachedOverview.latestTime ?? null }) @@ -234,6 +239,9 @@ export default function SnsPage() { const totalPosts = Math.max(0, Number(statsResult.data.totalPosts || 0)) const totalFriends = Math.max(0, Number(statsResult.data.totalFriends || 0)) + const myPosts = (typeof statsResult.data.myPosts === 'number' && Number.isFinite(statsResult.data.myPosts) && statsResult.data.myPosts >= 0) + ? Math.floor(statsResult.data.myPosts) + : null let earliestTime: number | null = null let latestTime: number | null = null @@ -256,6 +264,7 @@ export default function SnsPage() { const nextOverviewStats = { totalPosts, totalFriends, + myPosts, earliestTime, latestTime } @@ -279,7 +288,8 @@ export default function SnsPage() { if (overviewStatsStatus === 'loading') { return '统计中...' } - return `共 ${overviewStats.totalPosts} 条 | ${formatDateOnly(overviewStats.earliestTime)} ~ ${formatDateOnly(overviewStats.latestTime)} | ${overviewStats.totalFriends} 位好友` + const myPostsLabel = overviewStats.myPosts === null ? '--' : String(overviewStats.myPosts) + return `共 ${overviewStats.totalPosts} 条 | 我的朋友圈 ${myPostsLabel} 条 | ${formatDateOnly(overviewStats.earliestTime)} ~ ${formatDateOnly(overviewStats.latestTime)} | ${overviewStats.totalFriends} 位好友` } const loadPosts = useCallback(async (options: { reset?: boolean, direction?: 'older' | 'newer' } = {}) => { diff --git a/src/services/config.ts b/src/services/config.ts index 6ed9c94..7625ca1 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -469,6 +469,7 @@ export interface ExportSnsStatsCacheItem { export interface SnsPageOverviewCache { totalPosts: number totalFriends: number + myPosts: number | null earliestTime: number | null latestTime: number | null } @@ -610,12 +611,18 @@ export async function getSnsPageCache(scopeKey: string): Promise 0) return Math.floor(v) return null } + const normalizeNullableCount = (v: unknown) => { + if (v === null || v === undefined) return null + if (typeof v === 'number' && Number.isFinite(v) && v >= 0) return Math.floor(v) + return null + } return { updatedAt: typeof raw.updatedAt === 'number' && Number.isFinite(raw.updatedAt) ? raw.updatedAt : 0, overviewStats: { totalPosts: Math.max(0, normalizeNumber(overviewObj.totalPosts)), totalFriends: Math.max(0, normalizeNumber(overviewObj.totalFriends)), + myPosts: normalizeNullableCount(overviewObj.myPosts), earliestTime: normalizeNullableTimestamp(overviewObj.earliestTime), latestTime: normalizeNullableTimestamp(overviewObj.latestTime) }, @@ -639,12 +646,18 @@ export async function setSnsPageCache( if (typeof v === 'number' && Number.isFinite(v) && v > 0) return Math.floor(v) return null } + const normalizeNullableCount = (v: unknown) => { + if (v === null || v === undefined) return null + if (typeof v === 'number' && Number.isFinite(v) && v >= 0) return Math.floor(v) + return null + } map[scopeKey] = { updatedAt: Date.now(), overviewStats: { totalPosts: normalizeNumber(payload?.overviewStats?.totalPosts), totalFriends: normalizeNumber(payload?.overviewStats?.totalFriends), + myPosts: normalizeNullableCount(payload?.overviewStats?.myPosts), earliestTime: normalizeNullableTimestamp(payload?.overviewStats?.earliestTime), latestTime: normalizeNullableTimestamp(payload?.overviewStats?.latestTime) }, diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index eca2038..2af6496 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -730,8 +730,8 @@ export interface ElectronAPI { selectExportDir: () => Promise<{ canceled: boolean; filePath?: string }> getSnsUsernames: () => Promise<{ success: boolean; usernames?: string[]; error?: string }> getUserPostCounts: () => Promise<{ success: boolean; data?: Record; error?: string }> - getExportStatsFast: () => Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number }; error?: string }> - getExportStats: () => Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number }; error?: string }> + getExportStatsFast: () => Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number; myPosts: number | null }; error?: string }> + getExportStats: () => Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number; myPosts: number | null }; error?: string }> installBlockDeleteTrigger: () => Promise<{ success: boolean; alreadyInstalled?: boolean; error?: string }> uninstallBlockDeleteTrigger: () => Promise<{ success: boolean; error?: string }> checkBlockDeleteTrigger: () => Promise<{ success: boolean; installed?: boolean; error?: string }>