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,6 +1,9 @@
import { useState, useEffect } from 'react';
import { Plus, Trash2, Pencil, RefreshCw, Loader2, Users, UserPlus, UserX, Shield } from 'lucide-react';
import { Plus, Trash2, Pencil, RefreshCw, Users, UserPlus, UserX, Shield } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { PageShell } from '@/components/layout/PageShell';
import { PageEmpty } from '@/components/layout/PageEmpty';
import { StatStrip } from '@/components/ui/stat-strip';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
@@ -78,35 +81,52 @@ export default function UserGroupsManager() {
const toggleExpand = (gid: number) => setExpanded(p => ({ ...p, [gid]: !p[gid] }));
const memberCount = groups.reduce((sum, g) => sum + (g.members || []).length, 0);
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold text-[var(--dg-text-primary)]">用户组管理</h2>
<p className="text-sm text-[var(--dg-text-secondary)] mt-1">创建用户组,将组授权给流水线,组内成员自动获得流水线权限</p>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={loadGroups}><RefreshCw className="w-4 h-4 mr-1" />刷新</Button>
<Button size="sm" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
<PageShell
variant="list"
title="用户组管理"
subtitle="创建用户组,将组授权给流水线,组内成员自动获得流水线权限"
icon={<Users className="h-5 w-5" />}
loading={loading}
metrics={(
<StatStrip
items={[
{ label: '用户组', value: groups.length, color: 'blue' },
{ label: '成员总数', value: memberCount, color: 'purple' },
]}
/>
)}
actions={(
<>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={loadGroups}>
<RefreshCw className="w-4 h-4 mr-1" />刷新
</Button>
<Button size="sm" className="dg-btn-primary" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新建用户组
</Button>
</div>
</div>
{loading ? (
<div className="flex justify-center py-12"><Loader2 className="w-6 h-6 animate-spin text-[var(--dg-text-secondary)]" /></div>
) : groups.length === 0 ? (
<div className="text-center py-12 text-[var(--dg-text-secondary)]">
<Users className="w-10 h-10 mx-auto mb-3 opacity-30" />
<p>暂无用户组,点击「新建用户组」创建</p>
</div>
</>
)}
>
{!loading && groups.length === 0 ? (
<PageEmpty
icon={<Users className="h-10 w-10" />}
title="暂无用户组"
description="点击「新建用户组」创建第一个用户组"
action={(
<Button size="sm" onClick={() => { setEditId(null); setFormName(''); setFormDesc(''); setFormOpen(true); }}>
<Plus className="w-4 h-4 mr-1" />新建用户组
</Button>
)}
/>
) : (
<div className="space-y-3">
{groups.map(g => (
<div key={g.id} className="border border-[var(--dg-border)] rounded-lg bg-[var(--dg-bg-secondary)]">
<div className="flex items-center justify-between p-4">
<div className="flex items-center gap-3" style={{ cursor: 'pointer' }} onClick={() => toggleExpand(g.id)}>
<Shield className="w-5 h-5 text-blue-400" />
<Shield className="w-5 h-5 dg-text-blue" />
<div>
<div className="font-medium text-[var(--dg-text-primary)]">{g.name}</div>
<div className="text-xs text-[var(--dg-text-secondary)]">{g.description || '暂无描述'} · {(g.members || []).length} 位成员</div>
@@ -139,7 +159,7 @@ export default function UserGroupsManager() {
{(g.members || []).map(m => (
<Badge key={m.id} variant="secondary" className="flex items-center gap-1 pl-2 pr-1 py-1.5">
<Users className="w-3 h-3" />{m.username}
<button className="ml-1 hover:text-red-400" onClick={() => handleRemoveMember(g.id, m.id, m.username)}>
<button className="ml-1 hover:dg-text-red" onClick={() => handleRemoveMember(g.id, m.id, m.username)}>
<UserX className="w-3 h-3" />
</button>
</Badge>
@@ -167,6 +187,6 @@ export default function UserGroupsManager() {
</div>
</DialogContent>
</Dialog>
</div>
</PageShell>
);
}