import { useEffect, useRef, useState } from 'react'; import { ClipboardList, Clock, CheckCircle, AlertTriangle, Database, ChevronRight, X, AlertCircle, Info, Bell } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { dbInstances, statusMap, statusColorMap, dbTypeColorMap, tickets, announcements } from '@/lib/mock'; import { useTheme } from '@/hooks/useTheme'; import { useAuth } from '@/hooks/useAuth'; import { dashboardApi } from '@/api/dashboard'; import type { DashboardStats } from '@/types'; import * as echarts from 'echarts'; const priorityStyle: Record = { urgent: { border: 'border-l-red-500', bg: 'bg-red-500/5', icon: AlertCircle, iconColor: 'text-red-400', badge: 'bg-red-500/15 text-red-400 border-red-500/30' }, high: { border: 'border-l-orange-500', bg: 'bg-orange-500/5', icon: AlertTriangle, iconColor: 'text-orange-400', badge: 'bg-orange-500/15 text-orange-400 border-orange-500/30' }, normal: { border: 'border-l-blue-500', bg: 'bg-blue-500/5', icon: Info, iconColor: 'text-blue-400', badge: 'bg-blue-500/15 text-blue-400 border-blue-500/30' }, low: { border: 'border-l-gray-500', bg: 'bg-gray-500/5', icon: Bell, iconColor: 'text-gray-400', badge: 'bg-gray-500/15 text-gray-400 border-gray-500/30' }, }; const priorityLabel: Record = { urgent: '紧急', high: '重要', normal: '通知', low: '一般' }; export default function Dashboard() { const chartRef = useRef(null); const { theme } = useTheme(); const { user } = useAuth(); const [dismissedIds, setDismissedIds] = useState>(new Set()); const [expandedId, setExpandedId] = useState(null); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { dashboardApi.getStats() .then(data => setStats(data)) .catch(() => { // fallback: keep stats null, show skeleton }) .finally(() => setLoading(false)); }, []); const visibleAnnouncements = announcements .filter(a => a.enabled && a.targetRoles.includes(user.role) && !dismissedIds.has(a.id)) .sort((a, b) => { if (a.pinned !== b.pinned) return a.pinned ? -1 : 1; const po = { urgent: 0, high: 1, normal: 2, low: 3 }; if (po[a.priority] !== po[b.priority]) return po[a.priority] - po[b.priority]; return new Date(b.publishTime).getTime() - new Date(a.publishTime).getTime(); }); const dismiss = (id: string) => setDismissedIds(prev => new Set(prev).add(id)); const toggleExpand = (id: string) => setExpandedId(prev => prev === id ? null : id); useEffect(() => { if (!chartRef.current) return; const chart = echarts.init(chartRef.current); const isLight = theme === 'light'; const colors = isLight ? { bg: 'rgba(255,255,255,0.95)', border: '#e2e5eb', text: '#1a1d23', muted: '#5c6370', grid: '#f0f1f5', blue: '#2563eb', green: '#16a34a', red: '#dc2626' } : { bg: 'rgba(22, 27, 34, 0.95)', border: '#30363d', text: '#f0f6fc', muted: '#8b949e', grid: '#21262d', blue: '#58a6ff', green: '#3fb950', red: '#f85149' }; const option: any = { backgroundColor: 'transparent', tooltip: { trigger: 'axis' as const, backgroundColor: colors.bg, borderColor: colors.border, textStyle: { color: colors.text }, }, legend: { data: ['提交工单', '执行成功', '执行失败'], textStyle: { color: colors.muted }, bottom: 0, itemWidth: 14, itemHeight: 8, }, grid: { left: '2%', right: '3%', bottom: '18%', top: '8%', containLabel: true }, xAxis: { type: 'category' as const, data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], axisLine: { lineStyle: { color: colors.border } }, axisLabel: { color: colors.muted, fontSize: 11 }, }, yAxis: { type: 'value' as const, axisLine: { show: false }, splitLine: { lineStyle: { color: colors.grid } }, axisLabel: { color: colors.muted, fontSize: 11 }, }, series: [ { name: '提交工单', type: 'line', smooth: true, data: [120, 132, 101, 134, 90, 230, 210], itemStyle: { color: colors.blue }, lineStyle: { width: 2 }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: isLight ? 'rgba(37, 99, 235, 0.15)' : 'rgba(88, 166, 255, 0.3)' }, { offset: 1, color: 'transparent' }, ]), }, symbol: 'none', }, { name: '执行成功', type: 'line', smooth: true, data: [110, 125, 95, 128, 85, 215, 200], itemStyle: { color: colors.green }, lineStyle: { width: 2 }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: isLight ? 'rgba(22, 163, 74, 0.15)' : 'rgba(63, 185, 80, 0.3)' }, { offset: 1, color: 'transparent' }, ]), }, symbol: 'none', }, { name: '执行失败', type: 'line', smooth: true, data: [5, 3, 4, 2, 3, 8, 5], itemStyle: { color: colors.red }, lineStyle: { width: 2 }, symbol: 'none', }, ], }; chart.setOption(option); const handleResize = () => chart.resize(); window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); chart.dispose(); }; }, [theme]); const onlineInstances = dbInstances.filter(i => i.status === 'online').length; const warningInstances = dbInstances.filter(i => i.status === 'warning').length; const offlineInstances = dbInstances.filter(i => i.status === 'offline').length; const pendingTickets = tickets.filter(t => t.status === 'pending' || t.status === 'reviewing').slice(0, 4); // Stats cards - use real API data const statsCards = stats ? [ { title: '工单总数', value: stats.ticket_total, icon: ClipboardList, color: 'text-blue-400', bg: 'bg-blue-400/10' }, { title: '待审批', value: stats.ticket_pending, icon: Clock, color: 'text-orange-400', bg: 'bg-orange-400/10' }, { title: '本月工单', value: stats.ticket_month, icon: CheckCircle, color: 'text-purple-400', bg: 'bg-purple-400/10' }, { title: '今日查询', value: stats.queries_today.toLocaleString(), icon: AlertTriangle, color: 'text-red-400', bg: 'bg-red-400/10' }, ] : []; return (
{/* 公告通知栏 */} {visibleAnnouncements.length > 0 && (
{visibleAnnouncements.slice(0, 3).map(ann => { const style = priorityStyle[ann.priority] || priorityStyle.normal; const PIcon = style.icon; const isExpanded = expandedId === ann.id; return (
{priorityLabel[ann.priority]} {ann.title} {ann.pinned && 置顶}
{ann.content}
{ann.content.includes('\n') && ( )}
{ann.publishTime.slice(0, 10)}
); })} {visibleAnnouncements.length > 3 && (
还有 {visibleAnnouncements.length - 3} 条公告,可在「公告管理」中查看
)}
)} {/* 统计卡片 */}
{loading ? ( Array.from({ length: 4 }).map((_, i) => (
)) ) : ( statsCards.map((s, i) => (
{s.title}
{s.value}
实例 {stats?.instance_online}/{stats?.instance_total} 在线 · {stats?.user_total} 用户
)) )}
{/* 图表 + 实例状态 */}
{/* 工单趋势图 */}
工单趋势
{['近7天', '近30天', '近90天'].map((p, i) => ( ))}
{/* 实例状态 */} 实例状态
{onlineInstances}
在线
{warningInstances}
告警
{offlineInstances}
离线
{dbInstances.slice(0, 4).map(inst => (
{inst.name}
{inst.host}:{inst.port}
{inst.status === 'online' ? '在线' : inst.status === 'warning' ? '告警' : '离线'}
))}
{/* 待处理工单 */} 待处理工单
{pendingTickets.map(ticket => ( ))}
工单号 标题 数据库 申请人 状态 创建时间 操作
{ticket.id} {ticket.title} {ticket.dbType.toUpperCase()} {ticket.applicant} {statusMap[ticket.status]} {ticket.createTime}
); }