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

+39 -34
View File
@@ -9,6 +9,9 @@ import { Label } from '@/components/ui/label';
import { toast } from 'sonner';
import { deployApi, userApi, pipelineApi } from '@/api';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { PageShell } from '@/components/layout/PageShell';
import { PageEmpty } from '@/components/layout/PageEmpty';
import { StatStrip } from '@/components/ui/stat-strip';
import type { JsonData } from '@/types/common';
interface ItemRole {
@@ -159,36 +162,41 @@ export default function DeployRBAC() {
};
const roleBadgeClass = (role: string) =>
role === 'owner' ? 'bg-amber-500/20 text-amber-400 border-amber-500/30' :
role === 'editor' ? 'bg-blue-500/20 text-blue-400 border-blue-500/30' :
'bg-slate-500/20 text-slate-400 border-slate-500/30';
role === 'owner' ? 'bg-amber-500/20 dg-text-yellow border-amber-500/30' :
role === 'editor' ? 'bg-blue-500/20 dg-text-blue border-blue-500/30' :
'bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)]';
const selectedUser = users.find(u => String(u.id) === selectedUserId);
if (loading) return <div className="flex items-center justify-center py-20"><Loader2 className="w-6 h-6 animate-spin" /></div>;
const activeRoles = roles.filter(r => r.status === 1).length;
return (
<div className="space-y-4">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-xl font-bold flex items-center gap-2 text-[var(--dg-text-primary)]">
<Shield className="w-5 h-5 text-purple-500" />发布权限
</h1>
<p className="text-sm text-[var(--dg-text-secondary)] mt-1">
Jenkins-style Role Strategy — 按流水线 code 正则匹配分配角色
</p>
</div>
<Button onClick={() => setShowCreate(true)} disabled={showCreate}>
<PageShell
variant="list"
title="发布权限"
subtitle="Jenkins-style Role Strategy — 按流水线 code 正则匹配分配角色"
icon={<Shield className="h-5 w-5" />}
loading={loading}
metrics={(
<StatStrip
items={[
{ label: 'Item Role', value: roles.length, color: 'purple' },
{ label: '已启用', value: activeRoles, color: 'emerald' },
{ label: '流水线', value: pipelines.length, color: 'blue' },
]}
/>
)}
actions={(
<Button className="dg-btn-primary" onClick={() => setShowCreate(true)} disabled={showCreate}>
<Plus className="w-4 h-4 mr-1" />新建角色
</Button>
</div>
)}
>
{/* ====== Pipeline Preview ====== */}
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-3">
<GitBranch className="w-4 h-4 text-blue-500" />
<GitBranch className="w-4 h-4 dg-text-blue" />
<span className="text-sm font-medium text-[var(--dg-text-primary)]">流水线命中预览</span>
<span className="text-xs text-[var(--dg-text-muted)]">— 选择一个角色查看哪些流水线会被匹配</span>
</div>
@@ -215,7 +223,7 @@ export default function DeployRBAC() {
})
}
{previewRoleId !== null && previewMatches.length === 0 && (
<span className="text-xs text-red-400">无匹配 — 请调整正则表达式</span>
<span className="text-xs dg-text-red">无匹配 — 请调整正则表达式</span>
)}
</div>
</CardContent>
@@ -266,10 +274,7 @@ export default function DeployRBAC() {
{/* Role list */}
{roles.length === 0 ? (
<div className="text-center py-12 text-[var(--dg-text-secondary)]">
<Shield className="w-10 h-10 mx-auto mb-2 opacity-20" />
<p className="text-sm">暂无 Item Role</p>
</div>
<PageEmpty icon={<Shield className="h-10 w-10" />} title="暂无 Item Role" description="创建角色后按正则匹配流水线并分配给用户" />
) : (
<div className="space-y-2">
{roles.map(role => (
@@ -278,21 +283,21 @@ export default function DeployRBAC() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 flex-1 min-w-0">
<div className="w-9 h-9 rounded-lg bg-purple-500/20 flex items-center justify-center shrink-0">
<Regex className="w-4 h-4 text-purple-400" />
<Regex className="w-4 h-4 dg-text-purple" />
</div>
<div className="min-w-0">
<div className="flex items-center gap-1.5 flex-wrap">
<span className="text-sm font-semibold text-[var(--dg-text-primary)]">{role.name}</span>
<Badge className={`text-xs border ${roleBadgeClass(role.role)}`}>{role.role === 'owner' ? '拥有者' : role.role === 'editor' ? '编辑者' : '查看者'}</Badge>
{role.status === 0 && <Badge variant="secondary" className="text-xs text-red-400">已禁用</Badge>}
{role.status === 0 && <Badge variant="secondary" className="text-xs dg-text-red">已禁用</Badge>}
<Badge variant="secondary" className="text-xs cursor-pointer" onClick={() => setPreviewRoleId(previewRoleId === role.id ? null : role.id)}>
<GitBranch className="w-3 h-3 mr-0.5" />{matchCount[role.id]?.length || 0} 条
</Badge>
</div>
<div className="text-xs text-[var(--dg-text-secondary)] mt-0.5 truncate">
<code className="font-mono text-purple-400 bg-purple-500/10 px-1 rounded">{role.code}</code>
<code className="font-mono dg-text-purple bg-purple-500/10 px-1 rounded">{role.code}</code>
<span className="mx-1">→</span>
<code className="font-mono text-blue-400 bg-blue-500/10 px-1 rounded">{role.pattern}</code>
<code className="font-mono dg-text-blue bg-blue-500/10 px-1 rounded">{role.pattern}</code>
{role.description && <span className="ml-2 opacity-60">{role.description}</span>}
</div>
</div>
@@ -309,8 +314,8 @@ export default function DeployRBAC() {
<SelectItem value="viewer">查看者</SelectItem>
</SelectContent>
</Select>
<Button size="icon" variant="ghost" className="h-7 w-7 text-green-400" onClick={() => handleUpdate(role.id)}><Check className="w-3.5 h-3.5" /></Button>
<Button size="icon" variant="ghost" className="h-7 w-7 text-red-400" onClick={() => setEditing(null)}><X className="w-3.5 h-3.5" /></Button>
<Button size="icon" variant="ghost" className="h-7 w-7 dg-text-green" onClick={() => handleUpdate(role.id)}><Check className="w-3.5 h-3.5" /></Button>
<Button size="icon" variant="ghost" className="h-7 w-7 dg-text-red" onClick={() => setEditing(null)}><X className="w-3.5 h-3.5" /></Button>
</>
) : (
<>
@@ -331,7 +336,7 @@ export default function DeployRBAC() {
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-3">
<UserPlus className="w-4 h-4 text-amber-400" />
<UserPlus className="w-4 h-4 dg-text-yellow" />
<span className="text-sm font-medium text-[var(--dg-text-primary)]">分配角色给用户</span>
</div>
<div className="flex gap-3 items-end mb-4">
@@ -344,7 +349,7 @@ export default function DeployRBAC() {
{users.map(u => (
<SelectItem key={u.id} value={String(u.id)}>
{u.real_name || u.username} ({u.username})
{assignments.some(a => a.user_id === u.id) && <span className="ml-2 text-xs text-purple-400">已分配</span>}
{assignments.some(a => a.user_id === u.id) && <span className="ml-2 text-xs dg-text-purple">已分配</span>}
</SelectItem>
))}
</SelectContent>
@@ -395,7 +400,7 @@ export default function DeployRBAC() {
</code>
)}
</div>
<Button variant="ghost" size="icon" className="h-6 w-6 text-red-400" onClick={() => handleRevoke(a.id)}>
<Button variant="ghost" size="icon" className="h-6 w-6 dg-text-red" onClick={() => handleRevoke(a.id)}>
<X className="w-3 h-3" />
</Button>
</div>
@@ -407,6 +412,6 @@ export default function DeployRBAC() {
)}
</CardContent>
</Card>
</div>
</PageShell>
);
}