/** * MonitorTab 组件 * * 系统监控仪表板Tab组件 * 从主页面拆分出来,负责显示系统监控数据 * 包括用户统计、素材统计、系统状态等信息 * 使用三层架构样式系统,支持深浅色主题 * * @created 2025-01-XX(拆分自 page.tsx) * @component */ 'use client' import { useEffect, useMemo } from 'react' import { useSession } from 'next-auth/react' import { useThemeStyles, useTheme, cn } from '@/lib/ui-styles' import { formatDateTimeLocale } from '../_utils/formatters' import type { ToastFn, DashboardData } from './types' import { useDashboardData } from '../_hooks/useDashboardData' import LoadingState from './ui/LoadingState' import ErrorState from './ui/ErrorState' import EmptyState from './ui/EmptyState' interface MonitorTabProps { showToast: ToastFn } const AUTO_REFRESH_INTERVAL = 60_000 export default function MonitorTab({ showToast }: MonitorTabProps) { const styles = useThemeStyles() const theme = useTheme() // 根据主题选择颜色类名 const borderColor = theme === 'dark' ? 'border-slate-800/70' : 'border-slate-200/70' const textTitle = theme === 'dark' ? 'text-white' : 'text-slate-900' const textDesc = theme === 'dark' ? 'text-slate-400' : 'text-slate-500' const textLabel = theme === 'dark' ? 'text-slate-400' : 'text-slate-500' const textValue = theme === 'dark' ? 'text-white' : 'text-slate-900' const borderRow = theme === 'dark' ? 'border-slate-700' : 'border-slate-200' const textRow = theme === 'dark' ? 'text-slate-400' : 'text-slate-600' const { data: session } = useSession() const currentIsSuperAdmin = Boolean((session?.user as any)?.isSuperAdmin) const { data, loading, error, refresh, updateSystemSetting, lastUpdated, refreshing } = useDashboardData({ showToast }) useEffect(() => { refresh() }, [refresh]) useEffect(() => { const timer = setInterval(() => { void refresh({ background: true }) }, AUTO_REFRESH_INTERVAL) return () => clearInterval(timer) }, [refresh]) const materialUploadMax = useMemo(() => { if (!data?.trends.materialUploads?.length) return 1 return Math.max(...data.trends.materialUploads.map((item) => item.count), 1) }, [data?.trends.materialUploads]) const formattedLastUpdated = useMemo(() => { if (!lastUpdated) return '尚未获取' return formatDateTimeLocale(lastUpdated, 'zh-CN', { hour12: false }) }, [lastUpdated]) const handleManualRefresh = () => { void refresh({ background: Boolean(data) }) } if (loading) { return } if (error) { return refresh()} /> } if (!data) { return } const handleToggle = (key: SystemSettingKey, value: boolean) => { void updateSystemSetting(key, value).catch(() => undefined) } return ( System Monitor 系统监控概览 最近更新:{formattedLastUpdated} {refreshing && ( 后台同步中... )} 刷新数据 {OVERVIEW_CARDS.map((card) => ( {card.icon} {card.label} {card.getValue(data)} ))} 📊 素材上传趋势(最近12个月) {data.trends.materialUploads.length ? ( {data.trends.materialUploads.map((item) => ( {item.date} {item.count} ))} ) : ( 暂无趋势数据 )} 🏢 部门分布 {data.distribution.departments.length ? ( data.distribution.departments.map((item) => ( {item.department || '未设置'} {item.count} )) ) : ( 暂无部门分布数据 )} 🛡️ 权限分布 {data.distribution.roles.length ? ( data.distribution.roles.map((item, index) => ( {item.role === 'ADMIN' && item.isSuperAdmin ? '超级管理员' : item.role === 'ADMIN' ? '管理员' : '普通用户'} {item.count} )) ) : ( 暂无权限分布数据 )} ⚙️ 系统信息 handleToggle('registrationEnabled', value)} activeText="开启" inactiveText="关闭" /> handleToggle('userActivationRequired', value)} activeText="需要" inactiveText="关闭" /> 数据库状态 {data.status.database === 'healthy' ? '正常' : '异常'} 运行时间 {Math.floor(data.status.uptime / 3600)} 小时 🕒 最近活动 素材标题 上传者 上传时间 {data.recentActivity.map((activity) => ( {activity.title} {activity.uploadedBy.username} {new Date(activity.createdAt).toLocaleString('zh-CN')} ))} ) } type SystemSettingKey = 'registrationEnabled' | 'userActivationRequired' const OVERVIEW_CARDS: Array<{ label: string icon: string className: string getValue: (data: DashboardData) => number }> = [ { label: '总用户数', icon: '👥', className: 'bg-blue-500 shadow-blue-500/40', getValue: (data) => data.overview.totalUsers, }, { label: '活跃用户', icon: '⚡', className: 'bg-green-500 shadow-green-500/40', getValue: (data) => data.overview.activeUsers, }, { label: '总素材数', icon: '📦', className: 'bg-purple-500 shadow-purple-500/40', getValue: (data) => data.overview.totalMaterials, }, { label: '本月新用户', icon: '🆕', className: 'bg-yellow-500 shadow-yellow-500/40', getValue: (data) => data.overview.newUsersThisMonth, }, { label: '本月新素材', icon: '📈', className: 'bg-indigo-500 shadow-indigo-500/40', getValue: (data) => data.overview.newMaterialsThisMonth, }, ] interface SystemToggleRowProps { label: string enabled: boolean editable: boolean onToggle: (value: boolean) => void activeText: string inactiveText: string } function SystemToggleRow({ label, enabled, editable, onToggle, activeText, inactiveText }: SystemToggleRowProps) { // 需要使用主题 Hook,但这个组件在函数外部定义,暂时保留 dark: 前缀 // 可以考虑将这个组件移到函数内部或传入 theme 参数 if (!editable) { return ( {label} {enabled ? activeText : inactiveText} ) } return ( {label} onToggle(event.target.checked)} /> ) }
System Monitor
最近更新:{formattedLastUpdated}
暂无部门分布数据
暂无权限分布数据