增加团队管理页面

This commit is contained in:
dengqichen 2025-10-29 14:20:50 +08:00
parent 714ced899f
commit 1886b51d18
2 changed files with 147 additions and 44 deletions

View File

@ -17,6 +17,12 @@ import {
} from '@/components/ui/select'; } from '@/components/ui/select';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { useToast } from '@/components/ui/use-toast'; import { useToast } from '@/components/ui/use-toast';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { import {
GitBranch, GitBranch,
FolderGit2, FolderGit2,
@ -43,7 +49,7 @@ import type {
} from './types'; } from './types';
import { import {
getGitInstances, getGitInstances,
getRepositoryGroupTree, getRepositoryGroups,
getRepositoryProjects, getRepositoryProjects,
getRepositoryBranches, getRepositoryBranches,
syncAllGitData, syncAllGitData,
@ -125,14 +131,56 @@ const GitManager: React.FC = () => {
} }
}; };
// 加载仓库组树 // 构建树形结构
const buildTree = (groups: RepositoryGroupResponse[]): RepositoryGroupResponse[] => {
// 使用 repoGroupId 作为 key因为 parentId 对应的是 Git 系统中的 repoGroupId
const map = new Map<number, RepositoryGroupResponse>();
const roots: RepositoryGroupResponse[] = [];
// 首先将所有节点放入 map使用 repoGroupId 作为 key
groups.forEach((group) => {
const key = group.repoGroupId || group.id;
map.set(key, { ...group, children: [] });
});
// 然后构建树形结构
groups.forEach((group) => {
const key = group.repoGroupId || group.id;
const node = map.get(key)!;
if (group.parentId) {
// parentId 对应的是父节点的 repoGroupId
const parent = map.get(group.parentId);
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
} else {
// 如果找不到父节点,作为根节点
roots.push(node);
}
} else {
// parentId 为 null作为根节点
roots.push(node);
}
});
return roots;
};
// 加载仓库组列表并构建树
const loadGroupTree = async () => { const loadGroupTree = async () => {
if (!selectedInstanceId) return; if (!selectedInstanceId) return;
setLoading((prev) => ({ ...prev, groups: true })); setLoading((prev) => ({ ...prev, groups: true }));
try { try {
const data = await getRepositoryGroupTree(selectedInstanceId); const data = await getRepositoryGroups({
setGroupTree(data || []); externalSystemId: selectedInstanceId,
});
// 构建树形结构
const tree = buildTree(data || []);
setGroupTree(tree);
} catch (error) { } catch (error) {
toast({ toast({
variant: 'destructive', variant: 'destructive',
@ -346,13 +394,24 @@ const GitManager: React.FC = () => {
return dayjs(time).fromNow(); return dayjs(time).fromNow();
}; };
// 渲染可见性图标 // 渲染可见性图标(带 Tooltip
const renderVisibilityIcon = (visibility: string) => { const renderVisibilityIcon = (visibility: string) => {
const config = const config =
VISIBILITY_CONFIG[visibility.toLowerCase() as keyof typeof VISIBILITY_CONFIG] || VISIBILITY_CONFIG[visibility.toLowerCase() as keyof typeof VISIBILITY_CONFIG] ||
VISIBILITY_CONFIG.public; VISIBILITY_CONFIG.public;
const Icon = config.icon; const Icon = config.icon;
return <Icon className={`h-3.5 w-3.5 ${config.color}`} />; return (
<Tooltip>
<TooltipTrigger asChild>
<div className="cursor-help">
<Icon className={`h-3.5 w-3.5 ${config.color}`} />
</div>
</TooltipTrigger>
<TooltipContent>
<p>{config.label}</p>
</TooltipContent>
</Tooltip>
);
}; };
// 过滤仓库组树 // 过滤仓库组树
@ -386,7 +445,7 @@ const GitManager: React.FC = () => {
return ( return (
<React.Fragment key={group.id}> <React.Fragment key={group.id}>
<div <div
className={`flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-accent rounded-md transition-colors ${ className={`group flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-accent rounded-md transition-colors ${
selectedGroup?.id === group.id ? 'bg-accent border-l-2 border-primary' : '' selectedGroup?.id === group.id ? 'bg-accent border-l-2 border-primary' : ''
}`} }`}
style={{ paddingLeft: `${level * 16 + 12}px` }} style={{ paddingLeft: `${level * 16 + 12}px` }}
@ -416,6 +475,27 @@ const GitManager: React.FC = () => {
<span className="flex-1 truncate text-sm font-medium"> <span className="flex-1 truncate text-sm font-medium">
{group.name} {group.name}
</span> </span>
{group.webUrl && (
<Tooltip>
<TooltipTrigger asChild>
<a
href={group.webUrl}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
>
<Button variant="ghost" size="icon" className="h-5 w-5">
<ExternalLink className="h-3 w-3" />
</Button>
</a>
</TooltipTrigger>
<TooltipContent>
<p> GitLab </p>
</TooltipContent>
</Tooltip>
)}
</div> </div>
{hasChildren && isExpanded && ( {hasChildren && isExpanded && (
@ -466,6 +546,7 @@ const GitManager: React.FC = () => {
const filteredGroupTree = useMemo(() => filterGroupTree(groupTree), [groupTree, groupSearch]); const filteredGroupTree = useMemo(() => filterGroupTree(groupTree), [groupTree, groupSearch]);
return ( return (
<TooltipProvider>
<div className="flex flex-col h-full p-6 space-y-4"> <div className="flex flex-col h-full p-6 space-y-4">
{/* 顶部标题栏 */} {/* 顶部标题栏 */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -672,6 +753,8 @@ const GitManager: React.FC = () => {
</div> </div>
</div> </div>
{project.webUrl && ( {project.webUrl && (
<Tooltip>
<TooltipTrigger asChild>
<a <a
href={project.webUrl} href={project.webUrl}
target="_blank" target="_blank"
@ -683,6 +766,11 @@ const GitManager: React.FC = () => {
<ExternalLink className="h-3 w-3" /> <ExternalLink className="h-3 w-3" />
</Button> </Button>
</a> </a>
</TooltipTrigger>
<TooltipContent>
<p> GitLab </p>
</TooltipContent>
</Tooltip>
)} )}
</div> </div>
</div> </div>
@ -740,7 +828,7 @@ const GitManager: React.FC = () => {
<div className="divide-y"> <div className="divide-y">
{filteredBranches.map((branch) => ( {filteredBranches.map((branch) => (
<div key={branch.id} className="p-3 hover:bg-accent transition-colors"> <div key={branch.id} className="p-3 hover:bg-accent transition-colors">
<div className="flex items-start gap-2 mb-2"> <div className="flex items-start gap-2">
<GitBranch className="h-4 w-4 mt-0.5 flex-shrink-0 text-muted-foreground" /> <GitBranch className="h-4 w-4 mt-0.5 flex-shrink-0 text-muted-foreground" />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
@ -748,15 +836,29 @@ const GitManager: React.FC = () => {
{branch.name} {branch.name}
</span> </span>
{branch.isDefaultBranch && ( {branch.isDefaultBranch && (
<Badge variant="default" className="text-xs"> <Tooltip>
<TooltipTrigger asChild>
<Badge variant="default" className="text-xs cursor-help">
</Badge> </Badge>
</TooltipTrigger>
<TooltipContent>
<p></p>
</TooltipContent>
</Tooltip>
)} )}
{branch.isProtected && ( {branch.isProtected && (
<Badge variant="secondary" className="text-xs flex items-center gap-1"> <Tooltip>
<TooltipTrigger asChild>
<Badge variant="secondary" className="text-xs flex items-center gap-1 cursor-help">
<Shield className="h-3 w-3" /> <Shield className="h-3 w-3" />
</Badge> </Badge>
</TooltipTrigger>
<TooltipContent>
<p></p>
</TooltipContent>
</Tooltip>
)} )}
</div> </div>
{branch.lastCommitMessage && ( {branch.lastCommitMessage && (
@ -782,19 +884,27 @@ const GitManager: React.FC = () => {
</div> </div>
)} )}
</div> </div>
</div>
{branch.webUrl && ( {branch.webUrl && (
<Tooltip>
<TooltipTrigger asChild>
<a <a
href={branch.webUrl} href={branch.webUrl}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-xs text-primary hover:underline inline-flex items-center gap-1" className="flex-shrink-0"
> >
<Button variant="ghost" size="icon" className="h-6 w-6">
<ExternalLink className="h-3 w-3" /> <ExternalLink className="h-3 w-3" />
Git </Button>
</a> </a>
</TooltipTrigger>
<TooltipContent>
<p> GitLab </p>
</TooltipContent>
</Tooltip>
)} )}
</div> </div>
</div>
))} ))}
</div> </div>
)} )}
@ -802,6 +912,7 @@ const GitManager: React.FC = () => {
</Card> </Card>
</div> </div>
</div> </div>
</TooltipProvider>
); );
}; };

View File

@ -35,14 +35,6 @@ export const getGitInstances = () =>
export const getRepositoryGroups = (params?: RepositoryGroupQuery) => export const getRepositoryGroups = (params?: RepositoryGroupQuery) =>
request.get<RepositoryGroupResponse[]>(`${GROUP_URL}/list`, { params }); request.get<RepositoryGroupResponse[]>(`${GROUP_URL}/list`, { params });
/**
*
*/
export const getRepositoryGroupTree = (externalSystemId: number) =>
request.get<RepositoryGroupResponse[]>(`${GROUP_URL}/tree`, {
params: { externalSystemId },
});
/** /**
* *
*/ */