feat: 全站 PageShell 设计系统与专业 UI 统一
- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架 - index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩) - 全站路由页接入 PageShell,云账单三 Tab 为标杆 - 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化 - 登录页/侧栏/顶栏去渐变光球,企业克制风 - 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
1 parent
1acb0c49af
commit
e363ded445
213 files changed
+7148
-6818
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user