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

+117 -80
View File
@@ -27,10 +27,16 @@ import {
CheckCircle2,
XCircle,
Loader2,
RefreshCw,
} from 'lucide-react';
import { regionApi } from '@/api';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { toast } from 'sonner';
import { PageShell } from '@/components/layout/PageShell';
import { ContentPanel } from '@/components/layout/ContentPanel';
import { PageLoading } from '@/components/layout/PageLoading';
import { PageEmpty } from '@/components/layout/PageEmpty';
import { StatStrip } from '@/components/ui/stat-strip';
import type { JsonData } from '@/types/common';
const providerNames: Record<string, string> = {
@@ -42,11 +48,11 @@ const providerNames: Record<string, string> = {
};
const providerColors: Record<string, string> = {
aws: 'text-orange-400',
aliyun: 'text-orange-400',
tencent: 'text-blue-400',
huawei: 'text-red-400',
azure: 'text-blue-400',
aws: 'dg-text-orange',
aliyun: 'dg-text-orange',
tencent: 'dg-text-blue',
huawei: 'dg-text-red',
azure: 'dg-text-blue',
};
export function RegionManager() {
@@ -171,9 +177,9 @@ export function RegionManager() {
{
key: 'status', header: '状态', sortable: true,
render: (r) => r.status === 'active' ? (
<Badge className="bg-emerald-500/20 text-emerald-400 gap-1"><CheckCircle2 className="h-3 w-3" /> 启用</Badge>
<Badge className="dg-status-running gap-1 border-0"><CheckCircle2 className="h-3 w-3" /> 启用</Badge>
) : (
<Badge className="bg-slate-500/20 text-slate-400 gap-1"><XCircle className="h-3 w-3" /> 禁用</Badge>
<Badge className="dg-status-stopped gap-1 border-0"><XCircle className="h-3 w-3" /> 禁用</Badge>
),
},
{
@@ -183,7 +189,7 @@ export function RegionManager() {
<button onClick={() => openEdit(r)} className="p-1.5 rounded hover:bg-[var(--dg-bg-active)] text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)]">
<Pencil className="h-3.5 w-3.5" />
</button>
<button onClick={() => handleDelete(r.id)} className="p-1.5 rounded hover:bg-[var(--dg-bg-active)] text-[var(--dg-text-muted)] hover:text-red-400">
<button onClick={() => handleDelete(r.id)} className="p-1.5 rounded hover:bg-[var(--dg-bg-active)] text-[var(--dg-text-muted)] hover:dg-text-red">
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
@@ -191,74 +197,105 @@ export function RegionManager() {
},
];
return (
<div className="p-6 space-y-4">
{/* 批量操作栏 */}
{selectedIds.length > 0 && (
<div className="flex items-center justify-between gap-4 px-4 py-2.5 rounded-lg bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)]">
<span className="text-sm text-[var(--dg-text-secondary)]">
已选择 <span className="font-semibold text-[var(--dg-text-primary)]">{selectedIds.length}</span> 个区域
</span>
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" onClick={() => setSelectedIds([])} className="text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)]">
取消选择
</Button>
<Button
variant="outline"
size="sm"
className="border-red-500/30 text-red-400 hover:bg-red-500/10"
onClick={handleBatchDelete}
>
<Trash2 className="h-4 w-4 mr-1.5" />
批量删除
</Button>
</div>
</div>
)}
const stats = {
total: regions.length,
active: regions.filter(r => r.status === 'active').length,
inactive: regions.filter(r => r.status !== 'active').length,
};
{/* DataTable */}
<DataTable
columns={columns}
data={filtered}
rowKey={(r) => r.id}
loading={loading}
searchable
searchPlaceholder="搜索区域名称或编码..."
searchKeys={['name', 'code']}
sortable
selectable
selectedIds={selectedIds}
onSelectionChange={setSelectedIds}
columnToggle
exportable
exportFilename="regions.csv"
emptyText="暂无区域数据"
emptyRender={() => (
<div className="flex flex-col items-center gap-3 py-8">
<MapPin className="h-12 w-12 text-[var(--dg-text-muted)] opacity-40" />
<p className="text-[var(--dg-text-muted)]">暂无区域数据</p>
</div>
)}
toolbarExtra={
<>
<Select value={selectedProvider} onValueChange={setSelectedProvider}>
<SelectTrigger className="w-32 h-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-secondary)]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">全部云商</SelectItem>
{Object.entries(providerNames).map(([k, v]) => (
<SelectItem key={k} value={k}>{v}</SelectItem>
))}
</SelectContent>
</Select>
<Button onClick={openCreate} size="sm" className="h-8 gap-1.5">
<Plus className="h-4 w-4" />
const toolbar = (
<Select value={selectedProvider} onValueChange={setSelectedProvider}>
<SelectTrigger className="w-32 h-9 dg-input">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">全部云商</SelectItem>
{Object.entries(providerNames).map(([k, v]) => (
<SelectItem key={k} value={k}>{v}</SelectItem>
))}
</SelectContent>
</Select>
);
const batchToolbar = selectedIds.length > 0 ? (
<div className="flex items-center gap-2">
<span className="text-sm dg-text-secondary">
已选择 <span className="font-semibold text-[var(--text-primary)]">{selectedIds.length}</span> 个区域
</span>
<Button variant="ghost" size="sm" onClick={() => setSelectedIds([])} className="dg-text-muted">
取消选择
</Button>
<Button variant="outline" size="sm" className="dg-btn-danger" onClick={handleBatchDelete}>
<Trash2 className="h-4 w-4 mr-1" />
批量删除
</Button>
</div>
) : undefined;
return (
<PageShell
variant="list"
title="区域管理"
subtitle="管理各云厂商的区域与接入端点"
icon={<MapPin className="h-5 w-5" />}
metrics={
!loading ? (
<StatStrip
items={[
{ label: '总计', value: stats.total, color: 'blue' },
{ label: '启用', value: stats.active, color: 'green' },
{ label: '禁用', value: stats.inactive, color: 'muted' },
]}
/>
) : undefined
}
toolbar={toolbar}
actions={(
<>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadData}>
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />
刷新
</Button>
<Button size="sm" className="dg-btn-primary" onClick={openCreate}>
<Plus className="h-4 w-4 mr-1" />
添加区域
</Button>
</>
)}
>
{loading ? (
<PageLoading />
) : filtered.length === 0 ? (
<PageEmpty
icon={<MapPin className="h-12 w-12" />}
title="暂无区域数据"
action={(
<Button size="sm" className="dg-btn-primary" onClick={openCreate}>
<Plus className="h-4 w-4 mr-1" />
添加区域
</Button>
</>
}
/>
)}
/>
) : (
<ContentPanel toolbar={batchToolbar}>
<DataTable
columns={columns}
data={filtered}
rowKey={(r) => r.id}
searchable
searchPlaceholder="搜索区域名称或编码..."
searchKeys={['name', 'code']}
sortable
selectable
selectedIds={selectedIds}
onSelectionChange={setSelectedIds}
columnToggle
exportable
exportFilename="regions.csv"
emptyText="暂无匹配区域"
/>
</ContentPanel>
)}
{/* Dialog */}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
@@ -275,7 +312,7 @@ export function RegionManager() {
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="如:华北-北京"
className="bg-slate-800/50 border-slate-700"
className="dg-input"
/>
</div>
<div className="space-y-1.5">
@@ -284,7 +321,7 @@ export function RegionManager() {
value={formData.code}
onChange={(e) => setFormData({ ...formData, code: e.target.value })}
placeholder="如:cn-north-1"
className="bg-slate-800/50 border-slate-700 font-mono"
className="dg-input font-mono"
disabled={!!editingItem}
/>
</div>
@@ -293,7 +330,7 @@ export function RegionManager() {
<div className="space-y-1.5">
<Label>云商</Label>
<Select value={formData.provider} onValueChange={(v) => setFormData({ ...formData, provider: v })}>
<SelectTrigger className="bg-slate-800/50 border-slate-700">
<SelectTrigger className="dg-input">
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -306,7 +343,7 @@ export function RegionManager() {
<div className="space-y-1.5">
<Label>状态</Label>
<Select value={formData.status} onValueChange={(v) => setFormData({ ...formData, status: v })}>
<SelectTrigger className="bg-slate-800/50 border-slate-700">
<SelectTrigger className="dg-input">
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -322,7 +359,7 @@ export function RegionManager() {
value={formData.location}
onChange={(e) => setFormData({ ...formData, location: e.target.value })}
placeholder="如:中国北京"
className="bg-slate-800/50 border-slate-700"
className="dg-input"
/>
</div>
<div className="space-y-1.5">
@@ -331,7 +368,7 @@ export function RegionManager() {
value={formData.endpoint}
onChange={(e) => setFormData({ ...formData, endpoint: e.target.value })}
placeholder="如:ecs.cn-north-1.aliyuncs.com"
className="bg-slate-800/50 border-slate-700 font-mono text-sm"
className="dg-input font-mono text-sm"
/>
</div>
</div>
@@ -344,6 +381,6 @@ export function RegionManager() {
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</PageShell>
);
}