import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { Calendar, Loader2, Sparkles, Users } from 'lucide-react' import { finishBackgroundTask, isBackgroundTaskCancelRequested, registerBackgroundTask, updateBackgroundTask } from '../services/backgroundTaskMonitor' import './AnnualReportPage.scss' type YearOption = number | 'all' type YearsLoadPayload = { years?: number[] done: boolean error?: string canceled?: boolean strategy?: 'cache' | 'native' | 'hybrid' phase?: 'cache' | 'native' | 'scan' | 'done' statusText?: string nativeElapsedMs?: number scanElapsedMs?: number totalElapsedMs?: number switched?: boolean nativeTimedOut?: boolean } const formatLoadElapsed = (ms: number) => { const totalSeconds = Math.max(0, ms) / 1000 if (totalSeconds < 60) return `${totalSeconds.toFixed(1)}s` const minutes = Math.floor(totalSeconds / 60) const seconds = Math.floor(totalSeconds % 60) return `${minutes}m ${String(seconds).padStart(2, '0')}s` } function AnnualReportPage() { const navigate = useNavigate() const [availableYears, setAvailableYears] = useState([]) const [selectedYear, setSelectedYear] = useState(null) const [selectedPairYear, setSelectedPairYear] = useState(null) const [isLoading, setIsLoading] = useState(true) const [isLoadingMoreYears, setIsLoadingMoreYears] = useState(false) const [hasYearsLoadFinished, setHasYearsLoadFinished] = useState(false) const [loadStrategy, setLoadStrategy] = useState<'cache' | 'native' | 'hybrid'>('native') const [loadPhase, setLoadPhase] = useState<'cache' | 'native' | 'scan' | 'done'>('native') const [loadStatusText, setLoadStatusText] = useState('准备加载年份数据...') const [nativeElapsedMs, setNativeElapsedMs] = useState(0) const [scanElapsedMs, setScanElapsedMs] = useState(0) const [totalElapsedMs, setTotalElapsedMs] = useState(0) const [hasSwitchedStrategy, setHasSwitchedStrategy] = useState(false) const [nativeTimedOut, setNativeTimedOut] = useState(false) const [isGenerating, setIsGenerating] = useState(false) const [loadError, setLoadError] = useState(null) useEffect(() => { let disposed = false let taskId = '' let uiTaskId = '' const applyLoadPayload = (payload: YearsLoadPayload) => { if (uiTaskId) { updateBackgroundTask(uiTaskId, { detail: payload.statusText || '正在加载可用年份', progressText: payload.done ? '已完成' : `${Array.isArray(payload.years) ? payload.years.length : 0} 个年份` }) } if (payload.strategy) setLoadStrategy(payload.strategy) if (payload.phase) setLoadPhase(payload.phase) if (typeof payload.statusText === 'string' && payload.statusText) setLoadStatusText(payload.statusText) if (typeof payload.nativeElapsedMs === 'number' && Number.isFinite(payload.nativeElapsedMs)) { setNativeElapsedMs(Math.max(0, payload.nativeElapsedMs)) } if (typeof payload.scanElapsedMs === 'number' && Number.isFinite(payload.scanElapsedMs)) { setScanElapsedMs(Math.max(0, payload.scanElapsedMs)) } if (typeof payload.totalElapsedMs === 'number' && Number.isFinite(payload.totalElapsedMs)) { setTotalElapsedMs(Math.max(0, payload.totalElapsedMs)) } if (typeof payload.switched === 'boolean') setHasSwitchedStrategy(payload.switched) if (typeof payload.nativeTimedOut === 'boolean') setNativeTimedOut(payload.nativeTimedOut) const years = Array.isArray(payload.years) ? payload.years : [] if (years.length > 0) { setAvailableYears(years) setSelectedYear((prev) => { if (prev === 'all') return prev if (typeof prev === 'number' && years.includes(prev)) return prev return years[0] }) setSelectedPairYear((prev) => { if (prev === 'all') return prev if (typeof prev === 'number' && years.includes(prev)) return prev return years[0] }) setIsLoading(false) } if (payload.error && !payload.canceled) { setLoadError(payload.error || '加载年度数据失败') } if (payload.done) { setIsLoading(false) setIsLoadingMoreYears(false) setHasYearsLoadFinished(true) setLoadPhase('done') if (uiTaskId) { finishBackgroundTask(uiTaskId, payload.canceled ? 'canceled' : 'completed', { detail: payload.canceled ? '年度报告年份加载已停止' : `年度报告年份加载完成,共 ${years.length} 个年份`, progressText: payload.canceled ? '已停止' : `${years.length} 个年份` }) } } else { setIsLoadingMoreYears(true) setHasYearsLoadFinished(false) } } const stopListen = window.electronAPI.annualReport.onAvailableYearsProgress((payload) => { if (disposed) return if (taskId && payload.taskId !== taskId) return if (!taskId) taskId = payload.taskId applyLoadPayload(payload) }) const startLoad = async () => { uiTaskId = registerBackgroundTask({ sourcePage: 'annualReport', title: '年度报告年份加载', detail: '准备使用原生快速模式加载年份', progressText: '初始化', cancelable: true, onCancel: async () => { if (taskId) { await window.electronAPI.annualReport.cancelAvailableYearsLoad(taskId) } } }) setIsLoading(true) setIsLoadingMoreYears(true) setHasYearsLoadFinished(false) setLoadStrategy('native') setLoadPhase('native') setLoadStatusText('准备使用原生快速模式加载年份...') setNativeElapsedMs(0) setScanElapsedMs(0) setTotalElapsedMs(0) setHasSwitchedStrategy(false) setNativeTimedOut(false) setLoadError(null) try { const startResult = await window.electronAPI.annualReport.startAvailableYearsLoad() if (!startResult.success || !startResult.taskId) { finishBackgroundTask(uiTaskId, 'failed', { detail: startResult.error || '加载年度数据失败' }) setLoadError(startResult.error || '加载年度数据失败') setIsLoading(false) setIsLoadingMoreYears(false) return } taskId = startResult.taskId if (startResult.snapshot) { applyLoadPayload(startResult.snapshot) } } catch (e) { console.error(e) finishBackgroundTask(uiTaskId, 'failed', { detail: String(e) }) setLoadError(String(e)) setIsLoading(false) setIsLoadingMoreYears(false) } } void startLoad() return () => { disposed = true stopListen() } }, []) const handleGenerateReport = async () => { if (selectedYear === null) return setIsGenerating(true) try { const yearParam = selectedYear === 'all' ? 0 : selectedYear navigate(`/annual-report/view?year=${yearParam}`) } catch (e) { console.error('生成报告失败:', e) } finally { setIsGenerating(false) } } const handleGenerateDualReport = () => { if (selectedPairYear === null) return const yearParam = selectedPairYear === 'all' ? 0 : selectedPairYear navigate(`/dual-report?year=${yearParam}`) } if (isLoading && availableYears.length === 0) { return (

正在加载年份数据(首批)...

加载方式:{getStrategyLabel({ loadStrategy, loadPhase, hasYearsLoadFinished, hasSwitchedStrategy, nativeTimedOut })}

状态:{loadStatusText || '正在加载年份数据...'}

原生耗时:{formatLoadElapsed(nativeElapsedMs)}{nativeTimedOut ? '(超时)' : ''} |{' '} 扫表耗时:{formatLoadElapsed(scanElapsedMs)} |{' '} 总耗时:{formatLoadElapsed(totalElapsedMs)}

) } if (availableYears.length === 0 && !isLoadingMoreYears) { return (

暂无聊天记录

{loadError || '请先解密数据库后再生成年度报告'}

) } const yearOptions: YearOption[] = availableYears.length > 0 ? ['all', ...availableYears] : [] const getYearLabel = (value: YearOption | null) => { if (!value) return '' return value === 'all' ? '全部时间' : `${value} 年` } const loadedYearCount = availableYears.length const isYearStatusComplete = hasYearsLoadFinished const strategyLabel = getStrategyLabel({ loadStrategy, loadPhase, hasYearsLoadFinished, hasSwitchedStrategy, nativeTimedOut }) const renderYearLoadStatus = () => (
{isYearStatusComplete ? ( <>全部年份已加载完毕 ) : ( <> 更多年份加载中 )}
) return (

年度报告

选择年份,回顾你在微信里的点点滴滴

{loadedYearCount > 0 && (

{isYearStatusComplete ? ( <>已显示 {loadedYearCount} 个年份,年份数据已全部加载完毕。总耗时 {formatLoadElapsed(totalElapsedMs)} ) : ( <> 已显示 {loadedYearCount} 个年份,正在补充更多年份 (已耗时 {formatLoadElapsed(totalElapsedMs)}) )}

)}

加载方式:{strategyLabel}

状态: {loadStatusText || (isYearStatusComplete ? '全部年份已加载完毕' : '正在加载年份数据...')}

原生耗时:{formatLoadElapsed(nativeElapsedMs)}{nativeTimedOut ? '(超时)' : ''} |{' '} 扫表耗时:{formatLoadElapsed(scanElapsedMs)} |{' '} 总耗时:{formatLoadElapsed(totalElapsedMs)}

总年度报告

包含所有会话与消息

{yearOptions.map(option => (
setSelectedYear(option)} > {option === 'all' ? '全部' : option} {option === 'all' ? '时间' : '年'}
))}
{renderYearLoadStatus()}

双人年度报告

选择一位好友,只看你们的私聊

私聊
{yearOptions.map(option => (
setSelectedPairYear(option)} > {option === 'all' ? '全部' : option} {option === 'all' ? '时间' : '年'}
))}
{renderYearLoadStatus()}

从聊天排行中选择好友生成双人报告

) } function getStrategyLabel(params: { loadStrategy: 'cache' | 'native' | 'hybrid' loadPhase: 'cache' | 'native' | 'scan' | 'done' hasYearsLoadFinished: boolean hasSwitchedStrategy: boolean nativeTimedOut: boolean }): string { const { loadStrategy, loadPhase, hasYearsLoadFinished, hasSwitchedStrategy, nativeTimedOut } = params if (loadStrategy === 'cache') return '缓存模式(快速)' if (hasYearsLoadFinished) { if (loadStrategy === 'native') return '原生快速模式' if (hasSwitchedStrategy || nativeTimedOut) return '混合策略(原生→扫表)' return '扫表兼容模式' } if (loadPhase === 'native') return '原生快速模式(优先)' if (loadPhase === 'scan') return '扫表兼容模式(回退)' return '混合策略' } export default AnnualReportPage