/** * 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}