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

@@ -1,9 +1,12 @@
import { useState, useEffect } from 'react';
import { Plus, Pencil, Trash2, RefreshCw, Loader2, GitBranch, Container, Globe, Wrench} from 'lucide-react';
import { Plus, Pencil, Trash2, RefreshCw, GitBranch, Container, Globe, Settings2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { PageShell } from '@/components/layout/PageShell';
import { ContentPanel } from '@/components/layout/ContentPanel';
import { PageEmpty } from '@/components/layout/PageEmpty';
import { StatStrip } from '@/components/ui/stat-strip';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
@@ -106,6 +109,7 @@ export default function DeployCredentials() {
const [editingId, setEditingId] = useState<number | null>(null);
const [envDialogOpen, setEnvDialogOpen] = useState(false);
const [editEnv, setEditEnv] = useState<Environment | null>(null);
const [activeTab, setActiveTab] = useState('repos');
const fetchAll = async () => {
setLoading(true);
@@ -134,34 +138,52 @@ export default function DeployCredentials() {
const envTypeLabel: Record<string, string> = { production: '生产', staging: '预发布', testing: '测试', development: '开发' };
const pageTabs = [
{ id: 'repos', label: 'Git 仓库', badge: repos.length || undefined },
{ id: 'registries', label: '镜像仓库', badge: registries.length || undefined },
{ id: 'environments', label: '环境管理', badge: environments.length || undefined },
{ id: 'tools', label: '工具配置' },
];
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-[var(--dg-text-primary)]">发布配置管理</h2>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={fetchAll}><RefreshCw className="w-4 h-4 mr-1" />刷新</Button>
<Button size="sm" onClick={() => { setEditType('repo'); setEditingId(null); setEditorOpen(true); }}>
<PageShell
variant="list"
title="发布配置管理"
subtitle="Git 仓库、镜像仓库、环境与工具配置"
icon={<Settings2 className="h-5 w-5" />}
loading={loading}
tabs={pageTabs}
activeTab={activeTab}
onTabChange={setActiveTab}
metrics={(
<StatStrip
items={[
{ label: 'Git 仓库', value: repos.length, color: 'blue' },
{ label: '镜像仓库', value: registries.length, color: 'cyan' },
{ label: '环境', value: environments.length, color: 'purple' },
]}
/>
)}
actions={(
<>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={fetchAll}>
<RefreshCw className="w-4 h-4 mr-1" />刷新
</Button>
<Button size="sm" className="dg-btn-primary" onClick={() => { setEditType('repo'); setEditingId(null); setEditorOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新增 Git 仓库
</Button>
<Button size="sm" variant="secondary" onClick={() => { setEditType('registry'); setEditingId(null); setEditorOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新增镜像仓库
</Button>
</div>
</div>
<Tabs defaultValue="repos">
<TabsList>
<TabsTrigger value="repos" className="gap-1"><GitBranch className="w-4 h-4" />Git 仓库 ({repos.length})</TabsTrigger>
<TabsTrigger value="registries" className="gap-1"><Container className="w-4 h-4" />镜像仓库 ({registries.length})</TabsTrigger>
<TabsTrigger value="environments" className="gap-1"><Globe className="w-4 h-4" />环境管理 ({environments.length})</TabsTrigger>
<TabsTrigger value="tools" className="gap-1"><Wrench className="w-4 h-4" />工具配置</TabsTrigger>
</TabsList>
<TabsContent value="repos" className="mt-3">
{loading ? <div className="text-center py-8 text-[var(--dg-text-secondary)]"><Loader2 className="w-5 h-5 animate-spin mx-auto mb-2" />加载中...</div>
: repos.length === 0 ? <div className="text-center py-8 text-[var(--dg-text-secondary)]">暂无 Git 仓库认证配置</div>
: (
<div className="rounded-md border border-[var(--dg-border)] overflow-hidden">
</>
)}
>
{activeTab === 'repos' && (
repos.length === 0 ? (
<PageEmpty icon={<GitBranch className="h-10 w-10" />} title="暂无 Git 仓库认证配置" />
) : (
<ContentPanel noPadding>
<div className="overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]">
<tr>
@@ -179,7 +201,7 @@ export default function DeployCredentials() {
<td className="px-3 py-2 text-[var(--dg-text-secondary)] hidden md:table-cell font-mono text-xs max-w-[300px] truncate">{r.url}</td>
<td className="px-3 py-2"><Badge variant="outline" className="text-xs">{r.auth_type}</Badge></td>
<td className="px-3 py-2">
<Badge className={r.enabled ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30' : 'bg-slate-500/20 text-slate-400 border-slate-500/30'}>{r.enabled ? '启用' : '禁用'}</Badge>
<Badge className={r.enabled ? 'bg-emerald-500/20 dg-text-green border-emerald-500/30' : 'bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)]'}>{r.enabled ? '启用' : '禁用'}</Badge>
</td>
<td className="px-3 py-2 text-right">
<div className="flex justify-end gap-1">
@@ -192,14 +214,16 @@ export default function DeployCredentials() {
</tbody>
</table>
</div>
)}
</TabsContent>
</ContentPanel>
)
)}
<TabsContent value="registries" className="mt-3">
{loading ? <div className="text-center py-8 text-[var(--dg-text-secondary)]"><Loader2 className="w-5 h-5 animate-spin mx-auto mb-2" />加载中...</div>
: registries.length === 0 ? <div className="text-center py-8 text-[var(--dg-text-secondary)]">暂无镜像仓库认证配置</div>
: (
<div className="rounded-md border border-[var(--dg-border)] overflow-hidden">
{activeTab === 'registries' && (
registries.length === 0 ? (
<PageEmpty icon={<Container className="h-10 w-10" />} title="暂无镜像仓库认证配置" />
) : (
<ContentPanel noPadding>
<div className="overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]">
<tr>
@@ -219,7 +243,7 @@ export default function DeployCredentials() {
<td className="px-3 py-2 text-[var(--dg-text-secondary)] hidden sm:table-cell">{r.namespace || '-'}</td>
<td className="px-3 py-2"><Badge variant="outline" className="text-xs">{r.auth_type}</Badge></td>
<td className="px-3 py-2">
<Badge className={r.enabled ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30' : 'bg-slate-500/20 text-slate-400 border-slate-500/30'}>{r.enabled ? '启用' : '禁用'}</Badge>
<Badge className={r.enabled ? 'bg-emerald-500/20 dg-text-green border-emerald-500/30' : 'bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)]'}>{r.enabled ? '启用' : '禁用'}</Badge>
</td>
<td className="px-3 py-2 text-right">
<div className="flex justify-end gap-1">
@@ -232,20 +256,31 @@ export default function DeployCredentials() {
</tbody>
</table>
</div>
)}
</TabsContent>
</ContentPanel>
)
)}
{/* Environments tab */}
<TabsContent value="environments" className="mt-3">
{activeTab === 'environments' && (
<>
<div className="flex items-center justify-end mb-3">
<Button size="sm" onClick={() => { setEditEnv(null); setEnvDialogOpen(true); }}>
<Button size="sm" className="dg-btn-primary" onClick={() => { setEditEnv(null); setEnvDialogOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新增环境
</Button>
</div>
{loading ? <div className="text-center py-8 text-[var(--dg-text-secondary)]"><Loader2 className="w-5 h-5 animate-spin mx-auto mb-2" />加载中...</div>
: environments.length === 0 ? <div className="text-center py-8 text-[var(--dg-text-secondary)]">暂无环境配置,点击"新增环境"创建</div>
: (
<div className="rounded-md border border-[var(--dg-border)] overflow-hidden">
{environments.length === 0 ? (
<PageEmpty
icon={<Globe className="h-10 w-10" />}
title="暂无环境配置"
description='点击"新增环境"创建'
action={(
<Button size="sm" onClick={() => { setEditEnv(null); setEnvDialogOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新增环境
</Button>
)}
/>
) : (
<ContentPanel noPadding>
<div className="overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]">
<tr>
@@ -272,7 +307,7 @@ export default function DeployCredentials() {
<td className="px-3 py-2"><div className="w-6 h-6 rounded border" style={{ backgroundColor: env.color }} /></td>
<td className="px-3 py-2 text-[var(--dg-text-secondary)]">{env.sort_order}</td>
<td className="px-3 py-2">
<Badge className={env.enabled ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30' : 'bg-slate-500/20 text-slate-400 border-slate-500/30'}>{env.enabled ? '启用' : '禁用'}</Badge>
<Badge className={env.enabled ? 'bg-emerald-500/20 dg-text-green border-emerald-500/30' : 'bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)]'}>{env.enabled ? '启用' : '禁用'}</Badge>
</td>
<td className="px-3 py-2 text-right">
<div className="flex justify-end gap-1">
@@ -284,15 +319,13 @@ export default function DeployCredentials() {
))}
</tbody>
</table>
</div>
</div>
</ContentPanel>
)}
</TabsContent>
</>
)}
{/* Tools config tab */}
<TabsContent value="tools" className="mt-3">
<ToolConfigTab />
</TabsContent>
</Tabs>
{activeTab === 'tools' && <ToolConfigTab />}
<CredentialFormDialog
open={editorOpen} onOpenChange={setEditorOpen} type={editType} editId={editingId}
@@ -300,6 +333,6 @@ export default function DeployCredentials() {
/>
<EnvFormDialog open={envDialogOpen} onClose={() => setEnvDialogOpen(false)} onSaved={fetchAll} editData={editEnv} />
</div>
</PageShell>
);
}