import { useEffect, useState, useCallback } from 'react'; import { Row, Col, Spin, Empty } from 'antd'; import { TeamOutlined, CalendarOutlined, BookOutlined, HeartOutlined, SafetyCertificateOutlined, FileTextOutlined, RightOutlined, PartitionOutlined, ClockCircleOutlined, CheckCircleOutlined, ThunderboltOutlined, SettingOutlined, UserOutlined, BellOutlined, ApartmentOutlined, SmileOutlined, } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import { useThemeMode } from '../hooks/useThemeMode'; import { useMessageStore } from '../stores/message'; import { listAuditLogs, type AuditLogItem } from '../api/auditLogs'; import { listPendingTasks, type TaskInfo } from '../api/workflowTasks'; import { useCountUp } from '../hooks/useCountUp'; // --- Shared utilities --- function formatTimeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime(); const minutes = Math.floor(diff / 60000); if (minutes < 1) return '刚刚'; if (minutes < 60) return `${minutes} 分钟前`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours} 小时前`; const days = Math.floor(hours / 24); return `${days} 天前`; } const ACTION_LABELS: Record = { create: '创建', created: '创建', update: '更新', updated: '更新', delete: '删除', deleted: '删除', login: '登录', 'user.create': '创建', 'user.update': '更新', 'user.delete': '删除', }; const RESOURCE_LABELS: Record = { user: '用户', role: '角色', organization: '组织', message: '消息', plugin: '插件', process_instance: '流程实例', }; const RESOURCE_ICONS: Record = { user: , role: , organization: , process_instance: , message: , }; function formatActionLabel(action: string): string { return ACTION_LABELS[action] || ACTION_LABELS[action.split('.').pop() || ''] || action; } function formatResourceLabel(resource: string): string { return RESOURCE_LABELS[resource] || RESOURCE_LABELS[resource.split('.').pop() || ''] || resource; } // --- Dashboard config --- interface QuickActionDef { icon: React.ReactNode; label: string; path: string; } const QUICK_ACTIONS: QuickActionDef[] = [ { icon: , label: '用户管理', path: '/users' }, { icon: , label: '角色权限', path: '/roles' }, { icon: , label: '工作流', path: '/workflow' }, { icon: , label: '消息管理', path: '/messages' }, { icon: , label: '系统设置', path: '/settings' }, { icon: , label: '审计日志', path: '/settings' }, ]; const STAT_BAR_COLORS: string[] = [ 'linear-gradient(90deg, #E07A5F, #E8907A)', 'linear-gradient(90deg, #81B29A, #8FBF9E)', 'linear-gradient(90deg, #F2CC8F, #D4B878)', 'linear-gradient(90deg, #D4A5A5, #C4A0A0)', ]; const STAT_TEXT_COLORS: string[] = ['#E07A5F', '#81B29A', '#F2CC8F', '#D4A5A5']; interface StatCardDef { key: string; title: string; icon: React.ReactNode; value: number; path: string; } const STATS: StatCardDef[] = [ { key: 'pending-tasks', title: '待办任务', icon: , value: 0, path: '/workflow' }, { key: 'users', title: '系统用户', icon: , value: 0, path: '/users' }, { key: 'messages', title: '消息通知', icon: , value: 0, path: '/messages' }, { key: 'logs', title: '操作日志', icon: , value: 0, path: '/settings' }, ]; // --- Components --- function StatValue({ value, loading }: { value: number; loading: boolean }) { const animatedValue = useCountUp(value); if (loading) return ; return {animatedValue.toLocaleString()}; } export default function Home() { const navigate = useNavigate(); const isDark = useThemeMode(); const fetchUnreadCount = useMessageStore((s) => s.fetchUnreadCount); const [pendingTasks, setPendingTasks] = useState([]); const [recentActivities, setRecentActivities] = useState([]); const [activitiesLoading, setActivitiesLoading] = useState(true); const [statsLoading, setStatsLoading] = useState(true); useEffect(() => { let cancelled = false; fetchUnreadCount(); listPendingTasks(1, 5) .then((result) => { if (!cancelled) setPendingTasks(result.data); }) .catch((err) => console.warn('[Home] 获取待办任务失败:', err)) .finally(() => { if (!cancelled) setStatsLoading(false); }); listAuditLogs({ page: 1, page_size: 5 }) .then((result) => { if (!cancelled) setRecentActivities(result.data.filter((a) => a.action !== 'login_failed')); if (!cancelled) STATS[3].value = result.total; }) .catch((err) => console.warn('[Home] 获取审计日志失败:', err)) .finally(() => { if (!cancelled) setActivitiesLoading(false); }); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 更新统计值 STATS[0].value = pendingTasks.length; const handleNavigate = useCallback((path: string) => { navigate(path); }, [navigate]); return (
{/* 欢迎语 */}

暖记管理后台

班级管理 · 日记审核 · 成长追踪

{/* 统计卡片行 */}
{STATS.map((def, i) => (
handleNavigate(def.path)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate(def.path); }} onMouseEnter={(e) => { e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.06)'; e.currentTarget.style.transform = 'translateY(-1px)'; }} onMouseLeave={(e) => { e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.transform = 'none'; }} >
{def.title}
))}
{/* 待办任务 + 最近动态 */}
待办任务 {pendingTasks.length} 项待处理
{pendingTasks.length === 0 ? ( ) : ( pendingTasks.map((task) => (
handleNavigate('/workflow')} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate('/workflow'); }} >
{task.node_name || task.definition_name || '流程任务'}
{task.definition_name || '工作流'} {task.status === 'pending' ? '待处理' : task.status}
一般
)) )}
最近动态
{activitiesLoading ? (
) : recentActivities.length === 0 ? ( ) : ( recentActivities.map((log) => (
{RESOURCE_ICONS[log.resource_type] || }
{formatActionLabel(log.action)}了{formatResourceLabel(log.resource_type)}
{formatTimeAgo(log.created_at)}
)) )}
{/* 快捷入口 */}
快捷入口
{QUICK_ACTIONS.map((action) => (
handleNavigate(action.path)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate(action.path); }} >
{action.icon}
{action.label}
))}
{/* 暖记介绍卡片 */}

欢迎使用暖记管理后台

暖记是一款温暖治愈风格的手账日记 App,以手写/涂鸦为核心输入方式,面向小学生首发。 通过管理后台可以管理班级、审核日记、追踪学生成长。

); }