feat: 全站 PageShell 设计系统与专业 UI 统一

- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架
- index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩)
- 全站路由页接入 PageShell,云账单三 Tab 为标杆
- 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化
- 登录页/侧栏/顶栏去渐变光球,企业克制风
- 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
Logan committed 2026-07-01 12:45:38 +08:00
1 parent 1acb0c49af
commit e363ded445
213 files changed
+7148 -6818

No files matched your search

@@ -12,9 +12,11 @@ import { StatStrip } from '@/components/ui/stat-strip';
import {
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
} from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Switch } from '@/components/ui/switch';
import { toast } from 'sonner';
import { PageShell } from '@/components/layout/PageShell';
import { PageEmpty } from '@/components/layout/PageEmpty';
import { PageLoading } from '@/components/layout/PageLoading';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { alertCenterApi, alertApi } from '@/api';
import { useAlerts, useAgentAlertRules, useAlertMutations } from '@/hooks/api';
@@ -178,30 +180,16 @@ const AlertCenter: React.FC = () => {
setPage(1); // 过滤条件变化时重置到第一页
};
return (
<div className="space-y-3">
{/* 统计 — 紧凑条 */}
<StatStrip
items={[
{ label: t('alert.stat.firing'), value: stats.firing, color: stats.firing > 0 ? 'red' : 'muted' },
{ label: t('alert.stat.critical'), value: stats.critical, color: 'red' },
{ label: t('alert.stat.warning'), value: stats.warning, color: 'amber' },
{ label: t('alert.stat.resolved'), value: stats.resolved, color: 'emerald' },
]}
/>
const pageTabs = [
{ id: 'alerts', label: t('alert.tab.alerts') },
{ id: 'rules', label: t('alert.tab.rules') },
{ id: 'silences', label: t('alert.tab.silences') },
{ id: 'contacts', label: t('alert.tab.contacts') },
{ id: 'groups', label: t('alert.tab.groups') },
];
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-5 lg:w-[600px] h-9">
<TabsTrigger value="alerts" className="text-xs">{t('alert.tab.alerts')}</TabsTrigger>
<TabsTrigger value="rules" className="text-xs">{t('alert.tab.rules')}</TabsTrigger>
<TabsTrigger value="silences" className="text-xs">{t('alert.tab.silences')}</TabsTrigger>
<TabsTrigger value="contacts" className="text-xs">{t('alert.tab.contacts')}</TabsTrigger>
<TabsTrigger value="groups" className="text-xs">{t('alert.tab.groups')}</TabsTrigger>
</TabsList>
{/* 告警列表 Tab */}
<TabsContent value="alerts" className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-2">
const alertsToolbar = activeTab === 'alerts' ? (
<div className="flex flex-wrap items-center justify-between gap-2 w-full">
<div className="flex items-center gap-2 flex-1 min-w-[280px]">
<div className="relative flex-1">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" />
@@ -235,16 +223,65 @@ const AlertCenter: React.FC = () => {
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<span>{t('alert.totalItems', { count: total })}</span>
<Button variant="outline" size="sm" onClick={() => refetchAlerts()} className="h-7 text-xs px-2">
<RefreshCw className="w-3 h-3 mr-1" />{t('common.refresh')}
</Button>
</div>
</div>
<div className="flex items-center gap-1 text-xs dg-text-muted">
<span>{t('alert.totalItems', { count: total })}</span>
<Button variant="outline" size="sm" onClick={() => refetchAlerts()} className="h-7 text-xs px-2 dg-btn-secondary">
<RefreshCw className="w-3 h-3 mr-1" />{t('common.refresh')}
</Button>
</div>
</div>
) : undefined;
{/* 告警表格 — 紧凑 */}
<Card>
return (
<PageShell
variant="list"
title="告警中心"
subtitle="告警事件处理、规则与通知配置"
icon={<AlertTriangle className="h-5 w-5" />}
tabs={pageTabs}
activeTab={activeTab}
onTabChange={setActiveTab}
toolbar={alertsToolbar}
metrics={(
<StatStrip
items={[
{ label: t('alert.stat.firing'), value: stats.firing, color: stats.firing > 0 ? 'red' : 'muted' },
{ label: t('alert.stat.critical'), value: stats.critical, color: 'red' },
{ label: t('alert.stat.warning'), value: stats.warning, color: 'amber' },
{ label: t('alert.stat.resolved'), value: stats.resolved, color: 'emerald' },
]}
/>
)}
actions={activeTab === 'rules' ? (
<>
<Button className="dg-btn-primary" onClick={handleOpenAddRule}>
<Plus className="w-4 h-4 mr-1" />{t('alert.addRule')}
</Button>
<Button variant="secondary" className="dg-btn-secondary" onClick={handleQuickSetup} disabled={quickSetupMutation.isPending}>
<Rocket className="w-4 h-4 mr-1" />
{quickSetupMutation.isPending ? t('alert.quickSetupCreating') : t('alert.quickSetup')}
</Button>
<Button variant="outline" className="dg-btn-secondary" onClick={() => refetchRules()}>
<RefreshCw className="w-4 h-4 mr-2" />{t('common.refresh')}
</Button>
</>
) : activeTab === 'contacts' ? (
<Button size="sm" className="dg-btn-primary" onClick={() => { setEditingContact(null); setContactDialogOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />添加联系人
</Button>
) : activeTab === 'groups' ? (
<>
<Button variant="outline" className="dg-btn-secondary" onClick={fetchContactGroups}>
<RefreshCw className="w-4 h-4 mr-2" />{t('common.refresh')}
</Button>
<Button className="dg-btn-primary" onClick={() => { setEditingGroup(null); setGroupDialogOpen(true); }}>
<Plus className="w-4 h-4 mr-2" />添加联系组
</Button>
</>
) : undefined}
>
{activeTab === 'alerts' && (
<Card>
<CardContent className="p-0">
<div className="overflow-x-auto">
<table className="w-full text-sm">
@@ -260,9 +297,9 @@ const AlertCenter: React.FC = () => {
</thead>
<tbody className="divide-y">
{loading ? (
<tr><td colSpan={6} className="px-3 py-6 text-center text-xs text-muted-foreground">{t('alert.loading')}</td></tr>
<tr><td colSpan={6} className="px-3 py-2"><PageLoading label={t('alert.loading')} /></td></tr>
) : alerts.length === 0 ? (
<tr><td colSpan={6} className="px-3 py-6 text-center text-xs text-muted-foreground">{t('alert.noAlerts')}</td></tr>
<tr><td colSpan={6} className="px-3 py-2"><PageEmpty title={t('alert.noAlerts')} className="py-6" /></td></tr>
) : (
alerts.filter(alert => {
// 客户端也保留关键词搜索过滤
@@ -302,7 +339,7 @@ const AlertCenter: React.FC = () => {
{alert.status === 'firing' && (
<Button variant="ghost" size="sm" className="h-7 w-7 p-0"
onClick={() => handleAcknowledge(alert.id)}>
<Check className="w-3.5 h-3.5 text-blue-600" />
<Check className="w-3.5 h-3.5 dg-text-blue" />
</Button>
)}
{(alert.status === 'firing' || alert.status === 'acknowledged') && (
@@ -353,31 +390,15 @@ const AlertCenter: React.FC = () => {
)}
</CardContent>
</Card>
</TabsContent>
)}
{/* 告警规则 Tab */}
<TabsContent value="rules" className="space-y-4">
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<Button onClick={handleOpenAddRule}>
<Plus className="w-4 h-4 mr-1" />{t('alert.addRule')}
</Button>
<Button variant="secondary" onClick={handleQuickSetup} disabled={quickSetupMutation.isPending}>
<Rocket className="w-4 h-4 mr-1" />
{quickSetupMutation.isPending ? t('alert.quickSetupCreating') : t('alert.quickSetup')}
</Button>
</div>
<Button variant="outline" onClick={() => refetchRules()}>
<RefreshCw className="w-4 h-4 mr-2" />
刷新
</Button>
</div>
{rules.length === 0 ? (
<div className="text-center py-12 text-muted-foreground">
<AlertTriangle className="w-10 h-10 mx-auto mb-2 opacity-50" />
<p className="text-sm">{t('alert.noRules')}</p>
<p className="text-xs mt-1">{t('alert.noRulesHint')}</p>
</div>
{activeTab === 'rules' && (
rules.length === 0 ? (
<PageEmpty
icon={<AlertTriangle className="w-10 h-10" />}
title={t('alert.noRules')}
description={t('alert.noRulesHint')}
/>
) : (
<div className="grid grid-cols-1 gap-4">
{rules.map((rule) => (
@@ -417,7 +438,7 @@ const AlertCenter: React.FC = () => {
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" onClick={() => handleEditRule(rule)}>{t('common.edit')}</Button>
<Button variant="ghost" size="sm" className="text-red-400" onClick={() => handleDeleteRule(rule)}>{t('common.delete')}</Button>
<Button variant="ghost" size="sm" className="dg-text-red" onClick={() => handleDeleteRule(rule)}>{t('common.delete')}</Button>
<Switch
checked={rule.enabled}
onCheckedChange={(checked) => toggleRule(String(rule.id), checked)}
@@ -428,27 +449,14 @@ const AlertCenter: React.FC = () => {
</Card>
))}
</div>
)}
</TabsContent>
)
)}
{/* 静默规则 Tab */}
<TabsContent value="silences">
<SilenceRuleTab />
</TabsContent>
{activeTab === 'silences' && <SilenceRuleTab />}
{/* 报警联系人 Tab */}
<TabsContent value="contacts" className="space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-sm font-medium">{t('alert.contactsTitle')}</h3>
<Button size="sm" onClick={() => { setEditingContact(null); setContactDialogOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />
添加联系人
</Button>
</div>
{contacts.length === 0 ? (
<div className="text-center py-8 text-muted-foreground border border-dashed rounded-lg text-xs">
暂无报警联系人,点击「添加联系人」创建
</div>
{activeTab === 'contacts' && (
contacts.length === 0 ? (
<PageEmpty title="暂无报警联系人" description="点击「添加联系人」创建" />
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{contacts.map((c) => (
@@ -492,25 +500,12 @@ const AlertCenter: React.FC = () => {
</Card>
))}
</div>
)}
</TabsContent>
)
)}
{/* 报警联系组 Tab */}
<TabsContent value="groups" className="space-y-4">
<div className="flex justify-between items-center">
<Button variant="outline" onClick={fetchContactGroups}>
<RefreshCw className="w-4 h-4 mr-2" />
刷新
</Button>
<Button onClick={() => { setEditingGroup(null); setGroupDialogOpen(true); }}>
<Plus className="w-4 h-4 mr-2" />
添加联系组
</Button>
</div>
{contactGroups.length === 0 ? (
<div className="text-center py-16 text-muted-foreground border border-dashed rounded-lg">
暂无报警联系组,点击「添加联系组」创建
</div>
{activeTab === 'groups' && (
contactGroups.length === 0 ? (
<PageEmpty title="暂无报警联系组" description="点击「添加联系组」创建" />
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{contactGroups.map((g) => {
@@ -554,9 +549,8 @@ const AlertCenter: React.FC = () => {
);
})}
</div>
)}
</TabsContent>
</Tabs>
)
)}
<AlertDetailDialog
alert={selectedAlert}
@@ -590,7 +584,7 @@ const AlertCenter: React.FC = () => {
contacts={contacts}
onSaved={() => { fetchContactGroups(); }}
/>
</div>
</PageShell>
);
};