This commit is contained in:
dengqichen 2025-01-10 14:07:47 +08:00
parent 8a15130a31
commit 85d7e573ad
3 changed files with 131 additions and 25 deletions

View File

@ -1,7 +1,7 @@
import React, {useEffect, useState} from 'react';
import type {Application, RepositoryGroup} from '../types';
import type {Application, RepositoryGroup, RepositoryProject} from '../types';
import {DevelopmentLanguageTypeEnum} from '../types';
import {createApplication, updateApplication, getRepositoryGroupList} from '../service';
import {createApplication, updateApplication, getRepositoryGroupList, getRepositoryProjects} from '../service';
import type {ExternalSystemResponse} from '@/pages/Deploy/External/types';
import {SystemType} from '@/pages/Deploy/External/types';
import {getExternalSystems} from '@/pages/Deploy/External/service';
@ -58,6 +58,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
}) => {
const [gitInstances, setGitInstances] = useState<ExternalSystemResponse[]>([]);
const [repositoryGroups, setRepositoryGroups] = useState<RepositoryGroup[]>([]);
const [repositoryProjects, setRepositoryProjects] = useState<RepositoryProject[]>([]);
const {toast} = useToast();
const isEdit = !!initialValues?.id;
const [selectedGitInstance, setSelectedGitInstance] = useState<ExternalSystemResponse>();
@ -74,6 +75,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
sort: 0,
gitInstanceId: undefined,
repositoryGroupId: undefined,
repositoryProjectId: undefined,
},
});
@ -113,6 +115,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
sort: initialValues.sort,
gitInstanceId: initialValues.gitInstanceId,
repositoryGroupId: initialValues.repositoryGroupId,
repositoryProjectId: initialValues.repositoryProjectId,
});
const gitInstance = gitInstances.find(instance => instance.id === initialValues.gitInstanceId);
if (gitInstance) {
@ -122,20 +125,21 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
}, [initialValues, form, gitInstances]);
// 当选择Git实例时获取对应的仓库组列表
const handleGitInstanceChange = async (gitInstanceId: number) => {
try {
const data = await getRepositoryGroupList(gitInstanceId);
setRepositoryGroups(data);
// 清空已选择的仓库组
form.setValue('repositoryGroupId', undefined);
} catch (error) {
console.error('Failed to fetch repository groups:', error);
toast({
title: "错误",
description: "获取仓库组列表失败",
variant: "destructive",
});
}
const handleGitInstanceChange = (gitInstanceId: number) => {
form.setValue('repositoryGroupId', undefined);
form.setValue('repositoryProjectId', undefined);
setRepositoryProjects([]);
fetchRepositoryGroups(gitInstanceId);
};
const fetchRepositoryGroups = async (externalSystemId: number) => {
const response = await getRepositoryGroupList(externalSystemId);
setRepositoryGroups(response || []);
};
const fetchRepositoryProjects = async (groupId: number) => {
const response = await getRepositoryProjects(groupId);
setRepositoryProjects(response || []);
};
const handleSubmit = async (values: ApplicationFormValues) => {
@ -241,7 +245,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
)}
/>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-3 gap-4">
<FormField
control={form.control}
name="gitInstanceId"
@ -251,7 +255,6 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
<Select
onValueChange={(value) => {
field.onChange(Number(value));
form.setValue('repositoryGroupId', undefined);
handleGitInstanceChange(Number(value));
}}
value={field.value?.toString()}
@ -282,6 +285,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
name="repositoryGroupId"
render={({field}) => {
const [searchValue, setSearchValue] = React.useState("");
const [open, setOpen] = React.useState(false);
const filteredGroups = repositoryGroups.filter(group =>
group.name.toLowerCase().includes(searchValue.toLowerCase())
);
@ -289,7 +293,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
return (
<FormItem>
<FormLabel></FormLabel>
<Popover>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl>
<Button
@ -337,12 +341,11 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
)}
onClick={() => {
form.setValue("repositoryGroupId", group.id);
form.setValue("repositoryProjectId", undefined);
setRepositoryProjects([]);
fetchRepositoryProjects(group.id);
setSearchValue("");
// 关闭 Popover
const popoverTrigger = document.querySelector('[role="combobox"]');
if (popoverTrigger) {
(popoverTrigger as HTMLElement).click();
}
setOpen(false);
}}
>
{group.name}
@ -360,6 +363,87 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
);
}}
/>
<FormField
control={form.control}
name="repositoryProjectId"
render={({field}) => {
const [searchValue, setSearchValue] = React.useState("");
const [open, setOpen] = React.useState(false);
const filteredProjects = repositoryProjects.filter(project =>
project.name.toLowerCase().includes(searchValue.toLowerCase())
);
return (
<FormItem>
<FormLabel></FormLabel>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
disabled={!form.watch('repositoryGroupId')}
className={cn(
"w-full justify-between",
!field.value && "text-muted-foreground"
)}
>
{field.value
? repositoryProjects.find(
(project) => project.projectId === field.value
)?.name
: !form.watch('repositoryGroupId')
? "请先选择代码仓库组"
: "请选择项目"}
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-[--radix-popover-trigger-width] p-0">
<div className="flex items-center border-b px-3">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<input
placeholder="搜索项目..."
className="flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
/>
</div>
<ScrollArea className="h-[200px]">
{filteredProjects.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
</div>
) : (
filteredProjects.map((project) => (
<div
key={project.projectId}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground",
project.projectId === field.value && "bg-accent text-accent-foreground"
)}
onClick={() => {
form.setValue("repositoryProjectId", project.projectId);
setSearchValue("");
setOpen(false);
}}
>
{project.name}
{project.projectId === field.value && (
<Check className="ml-auto h-4 w-4" />
)}
</div>
))
)}
</ScrollArea>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
);
}}
/>
</div>
<FormField

View File

@ -1,10 +1,11 @@
import request from '@/utils/request';
import type { CreateApplicationRequest, UpdateApplicationRequest, Application, ApplicationQuery, RepositoryGroup } from './types';
import type { CreateApplicationRequest, UpdateApplicationRequest, Application, ApplicationQuery, RepositoryGroup, RepositoryProject } from './types';
import type { Page } from '@/types/base';
import {DevelopmentLanguageType} from "./types";
const BASE_URL = '/api/v1/applications';
const REPOSITORY_GROUP_URL = '/api/v1/repository-group';
const REPOSITORY_PROJECT_URL = '/api/v1/repository-project';
// 创建应用
export const createApplication = (data: CreateApplicationRequest) =>
@ -43,3 +44,9 @@ export const getRepositoryGroupList = (externalSystemId: number) =>
request.get<RepositoryGroup[]>(`${REPOSITORY_GROUP_URL}/list`, {
params: { externalSystemId },
});
// 获取项目列表
export const getRepositoryProjects = (groupId: number) =>
request.get<RepositoryProject[]>(`${REPOSITORY_PROJECT_URL}/list`, {
params: { groupId },
});

View File

@ -64,3 +64,18 @@ export interface RepositoryGroup {
sort?: number;
enabled?: boolean;
}
export interface RepositoryProject {
name: string;
path: string;
description: string;
visibility: string;
groupId: number;
isDefaultBranch: string;
webUrl: string;
sshUrl: string;
httpUrl: string;
lastActivityAt: string;
externalSystemId: number;
projectId: number;
}