import { useState, useEffect, useCallback } from 'react' import { useLocation } from 'react-router-dom' import { Users, Clock, MessageSquare, Send, Inbox, Calendar, Loader2, RefreshCw, User, Medal } from 'lucide-react' import ReactECharts from 'echarts-for-react' import { useAnalyticsStore } from '../stores/analyticsStore' import { useThemeStore } from '../stores/themeStore' import './AnalyticsPage.scss' import { Avatar } from '../components/Avatar' function AnalyticsPage() { const [isLoading, setIsLoading] = useState(false) const [loadingStatus, setLoadingStatus] = useState('') const [error, setError] = useState(null) const [progress, setProgress] = useState(0) const themeMode = useThemeStore((state) => state.themeMode) const { statistics, rankings, timeDistribution, isLoaded, setStatistics, setRankings, setTimeDistribution, markLoaded } = useAnalyticsStore() const loadData = useCallback(async (forceRefresh = false) => { if (isLoaded && !forceRefresh) return setIsLoading(true) setError(null) setProgress(0) // 监听后台推送的进度 const removeListener = window.electronAPI.analytics.onProgress?.((payload: { status: string; progress: number }) => { setLoadingStatus(payload.status) setProgress(payload.progress) }) try { setLoadingStatus('正在统计消息数据...') const statsResult = await window.electronAPI.analytics.getOverallStatistics(forceRefresh) if (statsResult.success && statsResult.data) { setStatistics(statsResult.data) } else { setError(statsResult.error || '加载统计数据失败') setIsLoading(false) return } setLoadingStatus('正在分析联系人排名...') const rankingsResult = await window.electronAPI.analytics.getContactRankings(20) if (rankingsResult.success && rankingsResult.data) { setRankings(rankingsResult.data) } setLoadingStatus('正在计算时间分布...') const timeResult = await window.electronAPI.analytics.getTimeDistribution() if (timeResult.success && timeResult.data) { setTimeDistribution(timeResult.data) } markLoaded() } catch (e) { setError(String(e)) } finally { setIsLoading(false) if (removeListener) removeListener() } }, [isLoaded, markLoaded, setRankings, setStatistics, setTimeDistribution]) const location = useLocation() useEffect(() => { const force = location.state?.forceRefresh === true loadData(force) }, [location.state, loadData]) useEffect(() => { const handleChange = () => { loadData(true) } window.addEventListener('wxid-changed', handleChange as EventListener) return () => window.removeEventListener('wxid-changed', handleChange as EventListener) }, [loadData]) const handleRefresh = () => loadData(true) const formatDate = (timestamp: number | null) => { if (!timestamp) return '-' const date = new Date(timestamp * 1000) return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}` } const formatNumber = (num: number) => { if (num >= 10000) return (num / 10000).toFixed(1) + '万' return num.toLocaleString() } const getChartLabelColors = () => { if (typeof window === 'undefined') { return { text: '#333333', line: '#999999' } } const styles = getComputedStyle(document.documentElement) const text = styles.getPropertyValue('--text-primary').trim() || '#333333' const line = styles.getPropertyValue('--text-tertiary').trim() || '#999999' return { text, line } } const chartLabelColors = getChartLabelColors() const getTypeChartOption = () => { if (!statistics) return {} const data = [ { name: '文本', value: statistics.textMessages }, { name: '图片', value: statistics.imageMessages }, { name: '语音', value: statistics.voiceMessages }, { name: '视频', value: statistics.videoMessages }, { name: '表情', value: statistics.emojiMessages }, { name: '其他', value: statistics.otherMessages }, ].filter(d => d.value > 0) return { tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, series: [{ type: 'pie', radius: ['40%', '70%'], avoidLabelOverlap: false, itemStyle: { borderRadius: 8, borderColor: 'transparent', borderWidth: 0 }, label: { show: true, formatter: '{b}\n{d}%', textStyle: { color: chartLabelColors.text, textShadowBlur: 0, textShadowColor: 'transparent', textShadowOffsetX: 0, textShadowOffsetY: 0, textBorderWidth: 0, textBorderColor: 'transparent', }, }, labelLine: { lineStyle: { color: chartLabelColors.line, shadowBlur: 0, shadowColor: 'transparent', }, }, emphasis: { itemStyle: { shadowBlur: 0, shadowOffsetX: 0, shadowOffsetY: 0, }, label: { color: chartLabelColors.text, textShadowBlur: 0, textShadowColor: 'transparent', textBorderWidth: 0, textBorderColor: 'transparent', }, labelLine: { lineStyle: { color: chartLabelColors.line, shadowBlur: 0, shadowColor: 'transparent', }, }, }, data, }] } } const getSendReceiveOption = () => { if (!statistics) return {} return { tooltip: { trigger: 'item' }, series: [{ type: 'pie', radius: ['50%', '70%'], data: [ { name: '发送', value: statistics.sentMessages, itemStyle: { color: '#07c160' } }, { name: '接收', value: statistics.receivedMessages, itemStyle: { color: '#1989fa' } } ], label: { show: true, formatter: '{b}: {c}', textStyle: { color: chartLabelColors.text, textShadowBlur: 0, textShadowColor: 'transparent', textShadowOffsetX: 0, textShadowOffsetY: 0, textBorderWidth: 0, textBorderColor: 'transparent', }, }, labelLine: { lineStyle: { color: chartLabelColors.line, shadowBlur: 0, shadowColor: 'transparent', }, }, emphasis: { itemStyle: { shadowBlur: 0, shadowOffsetX: 0, shadowOffsetY: 0, }, label: { color: chartLabelColors.text, textShadowBlur: 0, textShadowColor: 'transparent', textBorderWidth: 0, textBorderColor: 'transparent', }, labelLine: { lineStyle: { color: chartLabelColors.line, shadowBlur: 0, shadowColor: 'transparent', }, }, }, }] } } const getHourlyOption = () => { if (!timeDistribution) return {} const hours = Array.from({ length: 24 }, (_, i) => i) const data = hours.map(h => timeDistribution.hourlyDistribution[h] || 0) return { tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: hours.map(h => `${h}时`) }, yAxis: { type: 'value' }, series: [{ type: 'bar', data, itemStyle: { color: '#07c160', borderRadius: [4, 4, 0, 0] } }] } } if (isLoading && !isLoaded) { return (

{loadingStatus}

{progress}%
) } if (error && !isLoaded) { return (

{error}

) } return ( <>

私聊分析

{formatNumber(statistics?.totalMessages || 0)} 总消息数
{formatNumber(statistics?.sentMessages || 0)} 发送消息
{formatNumber(statistics?.receivedMessages || 0)} 接收消息
{statistics?.activeDays || 0} 活跃天数
{statistics && (
数据范围: {formatDate(statistics.firstMessageTime)} - {formatDate(statistics.lastMessageTime)}
)}

消息类型分布

发送/接收比例

每小时消息分布

聊天排名 Top 20

{rankings.map((contact, index) => (
{index + 1}
{index < 3 &&
}
{contact.displayName} 发送 {contact.sentCount} / 接收 {contact.receivedCount}
{formatNumber(contact.messageCount)} 条
))}
) } export default AnalyticsPage