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 React, {useEffect, useState} from 'react';
import type {Application, RepositoryGroup} from '../types'; import type {Application, RepositoryGroup, RepositoryProject} from '../types';
import {DevelopmentLanguageTypeEnum} 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 type {ExternalSystemResponse} from '@/pages/Deploy/External/types';
import {SystemType} from '@/pages/Deploy/External/types'; import {SystemType} from '@/pages/Deploy/External/types';
import {getExternalSystems} from '@/pages/Deploy/External/service'; import {getExternalSystems} from '@/pages/Deploy/External/service';
@ -58,6 +58,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
}) => { }) => {
const [gitInstances, setGitInstances] = useState<ExternalSystemResponse[]>([]); const [gitInstances, setGitInstances] = useState<ExternalSystemResponse[]>([]);
const [repositoryGroups, setRepositoryGroups] = useState<RepositoryGroup[]>([]); const [repositoryGroups, setRepositoryGroups] = useState<RepositoryGroup[]>([]);
const [repositoryProjects, setRepositoryProjects] = useState<RepositoryProject[]>([]);
const {toast} = useToast(); const {toast} = useToast();
const isEdit = !!initialValues?.id; const isEdit = !!initialValues?.id;
const [selectedGitInstance, setSelectedGitInstance] = useState<ExternalSystemResponse>(); const [selectedGitInstance, setSelectedGitInstance] = useState<ExternalSystemResponse>();
@ -74,6 +75,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
sort: 0, sort: 0,
gitInstanceId: undefined, gitInstanceId: undefined,
repositoryGroupId: undefined, repositoryGroupId: undefined,
repositoryProjectId: undefined,
}, },
}); });
@ -113,6 +115,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
sort: initialValues.sort, sort: initialValues.sort,
gitInstanceId: initialValues.gitInstanceId, gitInstanceId: initialValues.gitInstanceId,
repositoryGroupId: initialValues.repositoryGroupId, repositoryGroupId: initialValues.repositoryGroupId,
repositoryProjectId: initialValues.repositoryProjectId,
}); });
const gitInstance = gitInstances.find(instance => instance.id === initialValues.gitInstanceId); const gitInstance = gitInstances.find(instance => instance.id === initialValues.gitInstanceId);
if (gitInstance) { if (gitInstance) {
@ -122,20 +125,21 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
}, [initialValues, form, gitInstances]); }, [initialValues, form, gitInstances]);
// 当选择Git实例时获取对应的仓库组列表 // 当选择Git实例时获取对应的仓库组列表
const handleGitInstanceChange = async (gitInstanceId: number) => { const handleGitInstanceChange = (gitInstanceId: number) => {
try { form.setValue('repositoryGroupId', undefined);
const data = await getRepositoryGroupList(gitInstanceId); form.setValue('repositoryProjectId', undefined);
setRepositoryGroups(data); setRepositoryProjects([]);
// 清空已选择的仓库组 fetchRepositoryGroups(gitInstanceId);
form.setValue('repositoryGroupId', undefined); };
} catch (error) {
console.error('Failed to fetch repository groups:', error); const fetchRepositoryGroups = async (externalSystemId: number) => {
toast({ const response = await getRepositoryGroupList(externalSystemId);
title: "错误", setRepositoryGroups(response || []);
description: "获取仓库组列表失败", };
variant: "destructive",
}); const fetchRepositoryProjects = async (groupId: number) => {
} const response = await getRepositoryProjects(groupId);
setRepositoryProjects(response || []);
}; };
const handleSubmit = async (values: ApplicationFormValues) => { 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 <FormField
control={form.control} control={form.control}
name="gitInstanceId" name="gitInstanceId"
@ -251,7 +255,6 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
<Select <Select
onValueChange={(value) => { onValueChange={(value) => {
field.onChange(Number(value)); field.onChange(Number(value));
form.setValue('repositoryGroupId', undefined);
handleGitInstanceChange(Number(value)); handleGitInstanceChange(Number(value));
}} }}
value={field.value?.toString()} value={field.value?.toString()}
@ -282,6 +285,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
name="repositoryGroupId" name="repositoryGroupId"
render={({field}) => { render={({field}) => {
const [searchValue, setSearchValue] = React.useState(""); const [searchValue, setSearchValue] = React.useState("");
const [open, setOpen] = React.useState(false);
const filteredGroups = repositoryGroups.filter(group => const filteredGroups = repositoryGroups.filter(group =>
group.name.toLowerCase().includes(searchValue.toLowerCase()) group.name.toLowerCase().includes(searchValue.toLowerCase())
); );
@ -289,7 +293,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
return ( return (
<FormItem> <FormItem>
<FormLabel></FormLabel> <FormLabel></FormLabel>
<Popover> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<FormControl> <FormControl>
<Button <Button
@ -337,12 +341,11 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
)} )}
onClick={() => { onClick={() => {
form.setValue("repositoryGroupId", group.id); form.setValue("repositoryGroupId", group.id);
form.setValue("repositoryProjectId", undefined);
setRepositoryProjects([]);
fetchRepositoryProjects(group.id);
setSearchValue(""); setSearchValue("");
// 关闭 Popover setOpen(false);
const popoverTrigger = document.querySelector('[role="combobox"]');
if (popoverTrigger) {
(popoverTrigger as HTMLElement).click();
}
}} }}
> >
{group.name} {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> </div>
<FormField <FormField

View File

@ -1,10 +1,11 @@
import request from '@/utils/request'; 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 type { Page } from '@/types/base';
import {DevelopmentLanguageType} from "./types"; import {DevelopmentLanguageType} from "./types";
const BASE_URL = '/api/v1/applications'; const BASE_URL = '/api/v1/applications';
const REPOSITORY_GROUP_URL = '/api/v1/repository-group'; const REPOSITORY_GROUP_URL = '/api/v1/repository-group';
const REPOSITORY_PROJECT_URL = '/api/v1/repository-project';
// 创建应用 // 创建应用
export const createApplication = (data: CreateApplicationRequest) => export const createApplication = (data: CreateApplicationRequest) =>
@ -43,3 +44,9 @@ export const getRepositoryGroupList = (externalSystemId: number) =>
request.get<RepositoryGroup[]>(`${REPOSITORY_GROUP_URL}/list`, { request.get<RepositoryGroup[]>(`${REPOSITORY_GROUP_URL}/list`, {
params: { externalSystemId }, 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; sort?: number;
enabled?: boolean; 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;
}