import { useState, useEffect, useRef, useCallback } from 'react' import { Users, BarChart3, Clock, Image, Loader2, RefreshCw, User, Medal, Search, X, ChevronLeft, Copy, Check } from 'lucide-react' import { Avatar } from '../components/Avatar' import ReactECharts from 'echarts-for-react' import DateRangePicker from '../components/DateRangePicker' import './GroupAnalyticsPage.scss' interface GroupChatInfo { username: string displayName: string memberCount: number avatarUrl?: string } interface GroupMember { username: string displayName: string avatarUrl?: string } interface GroupMessageRank { member: GroupMember messageCount: number } type AnalysisFunction = 'members' | 'ranking' | 'activeHours' | 'mediaStats' function GroupAnalyticsPage() { const [groups, setGroups] = useState([]) const [filteredGroups, setFilteredGroups] = useState([]) const [isLoading, setIsLoading] = useState(true) const [selectedGroup, setSelectedGroup] = useState(null) const [selectedFunction, setSelectedFunction] = useState(null) const [searchQuery, setSearchQuery] = useState('') // 功能数据 const [members, setMembers] = useState([]) const [rankings, setRankings] = useState([]) const [activeHours, setActiveHours] = useState>({}) const [mediaStats, setMediaStats] = useState<{ typeCounts: Array<{ type: number; name: string; count: number }>; total: number } | null>(null) const [functionLoading, setFunctionLoading] = useState(false) // 成员详情弹框 const [selectedMember, setSelectedMember] = useState(null) const [copiedField, setCopiedField] = useState(null) // 时间范围 const [startDate, setStartDate] = useState('') const [endDate, setEndDate] = useState('') const [dateRangeReady, setDateRangeReady] = useState(false) // 拖动调整宽度 const [sidebarWidth, setSidebarWidth] = useState(300) const [isResizing, setIsResizing] = useState(false) const containerRef = useRef(null) useEffect(() => { loadGroups() }, [loadGroups]) useEffect(() => { if (searchQuery) { setFilteredGroups(groups.filter(g => g.displayName.toLowerCase().includes(searchQuery.toLowerCase()))) } else { setFilteredGroups(groups) } }, [searchQuery, groups]) // 拖动调整宽度 useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isResizing || !containerRef.current) return const containerRect = containerRef.current.getBoundingClientRect() const newWidth = e.clientX - containerRect.left setSidebarWidth(Math.max(250, Math.min(450, newWidth))) } const handleMouseUp = () => setIsResizing(false) if (isResizing) { document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) } return () => { document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } }, [isResizing]) // 日期范围变化时自动刷新 useEffect(() => { if (dateRangeReady && selectedGroup && selectedFunction && selectedFunction !== 'members') { setDateRangeReady(false) loadFunctionData(selectedFunction) } }, [dateRangeReady]) const loadGroups = useCallback(async () => { setIsLoading(true) try { const result = await window.electronAPI.groupAnalytics.getGroupChats() if (result.success && result.data) { setGroups(result.data) setFilteredGroups(result.data) } } catch (e) { console.error(e) } finally { setIsLoading(false) } }, []) useEffect(() => { const handleChange = () => { setGroups([]) setFilteredGroups([]) setSelectedGroup(null) setSelectedFunction(null) setMembers([]) setRankings([]) setActiveHours({}) setMediaStats(null) void loadGroups() } window.addEventListener('wxid-changed', handleChange as EventListener) return () => window.removeEventListener('wxid-changed', handleChange as EventListener) }, [loadGroups]) const handleGroupSelect = (group: GroupChatInfo) => { if (selectedGroup?.username !== group.username) { setSelectedGroup(group) setSelectedFunction(null) } } const handleFunctionSelect = async (func: AnalysisFunction) => { if (!selectedGroup) return setSelectedFunction(func) await loadFunctionData(func) } const loadFunctionData = async (func: AnalysisFunction) => { if (!selectedGroup) return setFunctionLoading(true) // 计算时间戳 const startTime = startDate ? Math.floor(new Date(startDate).getTime() / 1000) : undefined const endTime = endDate ? Math.floor(new Date(endDate + 'T23:59:59').getTime() / 1000) : undefined try { switch (func) { case 'members': { const result = await window.electronAPI.groupAnalytics.getGroupMembers(selectedGroup.username) if (result.success && result.data) setMembers(result.data) break } case 'ranking': { const result = await window.electronAPI.groupAnalytics.getGroupMessageRanking(selectedGroup.username, 20, startTime, endTime) if (result.success && result.data) setRankings(result.data) break } case 'activeHours': { const result = await window.electronAPI.groupAnalytics.getGroupActiveHours(selectedGroup.username, startTime, endTime) if (result.success && result.data) setActiveHours(result.data.hourlyDistribution) break } case 'mediaStats': { const result = await window.electronAPI.groupAnalytics.getGroupMediaStats(selectedGroup.username, startTime, endTime) if (result.success && result.data) setMediaStats(result.data) break } } } catch (e) { console.error(e) } finally { setFunctionLoading(false) } } const formatNumber = (num: number) => { if (num >= 10000) return (num / 10000).toFixed(1) + '万' return num.toLocaleString() } const getHourlyOption = () => { const hours = Array.from({ length: 24 }, (_, i) => i) const data = hours.map(h => activeHours[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] } }] } } const getMediaOption = () => { if (!mediaStats || mediaStats.typeCounts.length === 0) return {} // 定义颜色映射 const colorMap: Record = { 1: '#3b82f6', // 文本 - 蓝色 3: '#22c55e', // 图片 - 绿色 34: '#f97316', // 语音 - 橙色 43: '#a855f7', // 视频 - 紫色 47: '#ec4899', // 表情包 - 粉色 49: '#14b8a6', // 链接/文件 - 青色 [-1]: '#6b7280', // 其他 - 灰色 } const data = mediaStats.typeCounts.map(item => ({ name: item.name, value: item.count, itemStyle: { color: colorMap[item.type] || '#6b7280' } })) return { tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, series: [{ type: 'pie', radius: ['40%', '70%'], center: ['50%', '50%'], itemStyle: { borderRadius: 8, borderColor: 'rgba(255,255,255,0.1)', borderWidth: 2 }, label: { show: true, formatter: (params: { name: string; percent: number }) => { // 只显示占比大于3%的标签 return params.percent > 3 ? `${params.name}\n${params.percent.toFixed(1)}%` : '' }, color: '#fff' }, labelLine: { show: true, length: 10, length2: 10 }, data }] } } const handleRefresh = () => { if (selectedFunction) { loadFunctionData(selectedFunction) } } const handleDateRangeComplete = () => { setDateRangeReady(true) } const handleMemberClick = (member: GroupMember) => { setSelectedMember(member) setCopiedField(null) } const handleCopy = async (text: string, field: string) => { try { await navigator.clipboard.writeText(text) setCopiedField(field) setTimeout(() => setCopiedField(null), 2000) } catch (e) { console.error('复制失败:', e) } } const renderMemberModal = () => { if (!selectedMember) return null return (
setSelectedMember(null)}>
e.stopPropagation()}>

{selectedMember.displayName}

微信ID {selectedMember.username}
昵称 {selectedMember.displayName}
) } const renderGroupList = () => (
setSearchQuery(e.target.value)} /> {searchQuery && ( )}
{isLoading ? (
{[1, 2, 3, 4, 5].map(i => (
))}
) : filteredGroups.length === 0 ? (

{searchQuery ? '未找到匹配的群聊' : '暂无群聊数据'}

) : ( filteredGroups.map(group => (
handleGroupSelect(group)} >
{group.displayName} {group.memberCount} 位成员
)) )}
) const renderFunctionMenu = () => (

{selectedGroup?.displayName}

{selectedGroup?.memberCount} 位成员

handleFunctionSelect('members')}> 群成员查看
handleFunctionSelect('ranking')}> 群聊发言排行
handleFunctionSelect('activeHours')}> 群聊活跃时段
handleFunctionSelect('mediaStats')}> 媒体内容统计
) const renderFunctionContent = () => { const getFunctionTitle = () => { switch (selectedFunction) { case 'members': return '群成员查看' case 'ranking': return '群聊发言排行' case 'activeHours': return '群聊活跃时段' case 'mediaStats': return '媒体内容统计' default: return '' } } const showDateRange = selectedFunction !== 'members' return (

{getFunctionTitle()}

{selectedGroup?.displayName}
{showDateRange && ( )}
{functionLoading ? (
) : ( <> {selectedFunction === 'members' && (
{members.map(member => (
handleMemberClick(member)}>
{member.displayName}
))}
)} {selectedFunction === 'ranking' && (
{rankings.map((item, index) => (
{index + 1}
{index < 3 &&
}
{item.member.displayName}
{formatNumber(item.messageCount)} 条
))}
)} {selectedFunction === 'activeHours' && (
)} {selectedFunction === 'mediaStats' && mediaStats && (
{mediaStats.typeCounts.map(item => { const colorMap: Record = { 1: '#3b82f6', 3: '#22c55e', 34: '#f97316', 43: '#a855f7', 47: '#ec4899', 49: '#14b8a6', [-1]: '#6b7280' } const percentage = mediaStats.total > 0 ? ((item.count / mediaStats.total) * 100).toFixed(1) : '0' return (
{item.name} {formatNumber(item.count)} 条 ({percentage}%)
) })}
总计 {formatNumber(mediaStats.total)} 条
)} )}
) } const renderDetailPanel = () => { if (!selectedGroup) { return (

请从左侧选择一个群聊进行分析

) } if (!selectedFunction) { return renderFunctionMenu() } return renderFunctionContent() } return (
{renderGroupList()}
setIsResizing(true)} />
{renderDetailPanel()}
{renderMemberModal()}
) } export default GroupAnalyticsPage