增加团队管理页面
This commit is contained in:
parent
714ced899f
commit
1886b51d18
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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 },
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步仓库组数据
|
* 同步仓库组数据
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user