diff --git a/frontend/src/pages/Deploy/Application/Category/service.ts b/frontend/src/pages/Deploy/Application/Category/service.ts new file mode 100644 index 00000000..54596078 --- /dev/null +++ b/frontend/src/pages/Deploy/Application/Category/service.ts @@ -0,0 +1,46 @@ +import request from '@/utils/request'; +import type { Page } from '@/types/base'; +import type { + ApplicationCategoryQuery, + ApplicationCategoryResponse, + ApplicationCategoryRequest, +} from './types'; + +const BASE_URL = '/api/v1/application-category'; + +/** + * 分页查询分类 + */ +export const getCategories = (params?: ApplicationCategoryQuery) => + request.get>(`${BASE_URL}/page`, { params }); + +/** + * 查询分类详情 + */ +export const getCategoryById = (id: number) => + request.get(`${BASE_URL}/${id}`); + +/** + * 查询所有启用的分类(常用) + */ +export const getEnabledCategories = () => + request.get(`${BASE_URL}/enabled`); + +/** + * 创建分类 + */ +export const createCategory = (data: ApplicationCategoryRequest) => + request.post(BASE_URL, data); + +/** + * 更新分类 + */ +export const updateCategory = (id: number, data: ApplicationCategoryRequest) => + request.put(`${BASE_URL}/${id}`, data); + +/** + * 删除分类 + */ +export const deleteCategory = (id: number) => + request.delete(`${BASE_URL}/${id}`); + diff --git a/frontend/src/pages/Deploy/Application/Category/types.ts b/frontend/src/pages/Deploy/Application/Category/types.ts new file mode 100644 index 00000000..3663ce0b --- /dev/null +++ b/frontend/src/pages/Deploy/Application/Category/types.ts @@ -0,0 +1,40 @@ +import { BaseQuery } from '@/types/base'; + +/** + * 应用分类查询参数 + */ +export interface ApplicationCategoryQuery extends BaseQuery { + name?: string; + code?: string; + enabled?: boolean; +} + +/** + * 应用分类响应 + */ +export interface ApplicationCategoryResponse { + id: number; + name: string; + code: string; + description?: string; + icon?: string; + sort: number; + enabled: boolean; + createBy?: string; + createTime?: string; + updateBy?: string; + updateTime?: string; +} + +/** + * 应用分类创建/更新请求 + */ +export interface ApplicationCategoryRequest { + name: string; + code: string; + description?: string; + icon?: string; + sort?: number; + enabled?: boolean; +} + diff --git a/frontend/src/pages/Deploy/Application/List/components/ApplicationModal.tsx b/frontend/src/pages/Deploy/Application/List/components/ApplicationModal.tsx index c840c67b..f558073a 100644 --- a/frontend/src/pages/Deploy/Application/List/components/ApplicationModal.tsx +++ b/frontend/src/pages/Deploy/Application/List/components/ApplicationModal.tsx @@ -5,6 +5,8 @@ import {createApplication, updateApplication, getRepositoryGroupList, getReposit import type {ExternalSystemResponse} from '@/pages/Deploy/External/types'; import {SystemType} from '@/pages/Deploy/External/types'; import {getExternalSystems} from '@/pages/Deploy/External/service'; +import {getEnabledCategories} from '../../Category/service'; +import type {ApplicationCategoryResponse} from '../../Category/types'; import { Dialog, DialogContent, @@ -35,7 +37,6 @@ import {useForm} from "react-hook-form"; import {zodResolver} from "@hookform/resolvers/zod"; import {applicationFormSchema, type ApplicationFormValues} from "../schema"; import {Textarea} from "@/components/ui/textarea"; -import {Command, CommandEmpty, CommandGroup, CommandInput, CommandItem} from "@/components/ui/command"; import {ScrollArea} from "@/components/ui/scroll-area"; import {Check, ChevronDown, Search} from "lucide-react"; import {cn} from "@/lib/utils"; @@ -46,7 +47,6 @@ interface ApplicationModalProps { onCancel: () => void; onSuccess: () => void; initialValues?: Application; - projectGroupId: number; } const ApplicationModal: React.FC = ({ @@ -54,14 +54,13 @@ const ApplicationModal: React.FC = ({ onCancel, onSuccess, initialValues, - projectGroupId, }) => { + const [categories, setCategories] = useState([]); const [externalSystems, setExternalSystems] = useState([]); const [repositoryGroups, setRepositoryGroups] = useState([]); const [repositoryProjects, setRepositoryProjects] = useState([]); const {toast} = useToast(); const isEdit = !!initialValues?.id; - const [selectedExternalSystem, setSelectedExternalSystem] = useState(); const form = useForm({ resolver: zodResolver(applicationFormSchema), @@ -69,16 +68,31 @@ const ApplicationModal: React.FC = ({ appCode: "", appName: "", appDesc: "", + categoryId: undefined, language: DevelopmentLanguageTypeEnum.JAVA, enabled: true, sort: 0, externalSystemId: undefined, repoGroupId: undefined, repoProjectId: undefined, - projectGroupId, }, }); + // 加载分类列表 + const loadCategories = async () => { + try { + const data = await getEnabledCategories(); + setCategories(data || []); + } catch (error) { + toast({ + variant: "destructive", + title: "加载分类失败", + description: error instanceof Error ? error.message : undefined, + duration: 3000, + }); + } + }; + // 加载外部系统列表 const loadExternalSystems = async () => { try { @@ -100,6 +114,7 @@ const ApplicationModal: React.FC = ({ }; useEffect(() => { + loadCategories(); loadExternalSystems(); }, []); @@ -110,10 +125,10 @@ const ApplicationModal: React.FC = ({ appCode: initialValues.appCode, appName: initialValues.appName, appDesc: initialValues.appDesc || "", + categoryId: initialValues.categoryId, language: initialValues.language, enabled: initialValues.enabled, sort: initialValues.sort, - projectGroupId: initialValues.projectGroupId, externalSystemId: initialValues.externalSystemId, repoGroupId: initialValues.repoGroupId, repoProjectId: initialValues.repoProjectId @@ -132,19 +147,23 @@ const ApplicationModal: React.FC = ({ }, [initialValues]); // 当选择外部系统时,获取对应的仓库组列表 - const handleExternalSystemChange = (externalSystemId: number) => { + const handleExternalSystemChange = (externalSystemId: number | undefined) => { form.setValue('repoGroupId', undefined); form.setValue('repoProjectId', undefined); setRepositoryProjects([]); - fetchRepositoryGroups(externalSystemId); + if (externalSystemId) { + fetchRepositoryGroups(externalSystemId); + } }; - const fetchRepositoryGroups = async (externalSystemId: number) => { + const fetchRepositoryGroups = async (externalSystemId: number | undefined) => { + if (!externalSystemId) return; const response = await getRepositoryGroupList(externalSystemId); setRepositoryGroups(response || []); }; - const fetchRepositoryProjects = async (repoGroupId: number) => { + const fetchRepositoryProjects = async (repoGroupId: number | undefined) => { + if (!repoGroupId) return; const response = await getRepositoryProjects(repoGroupId); setRepositoryProjects(response || []); }; @@ -156,14 +175,14 @@ const ApplicationModal: React.FC = ({ await updateApplication({ ...values, id: initialValues!.id, - }); + } as any); toast({ title: "更新成功", description: `应用 ${values.appName} 已更新`, duration: 3000, }); } else { - await createApplication(values); + await createApplication(values as any); toast({ title: "创建成功", description: `应用 ${values.appName} 已创建`, @@ -229,6 +248,36 @@ const ApplicationModal: React.FC = ({ )} /> + ( + + 应用分类 + + + + )} + /> + + +
= ({ "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", group.repoGroupId === field.value && "bg-accent text-accent-foreground" )} - onClick={() => { - form.setValue("repoGroupId", group.repoGroupId); - form.setValue("repoProjectId", undefined); - setRepositoryProjects([]); - fetchRepositoryProjects(group.repoGroupId); + onClick={() => { + const groupId = group.repoGroupId; + form.setValue("repoGroupId", groupId); + form.setValue("repoProjectId", undefined as any); + setRepositoryProjects([]); + fetchRepositoryProjects(groupId); setSearchValue(""); setOpen(false); }} @@ -547,10 +597,10 @@ const ApplicationModal: React.FC = ({ +
+ + {/* 表格 */} +
+ + + + 分类名称 + 分类代码 + 图标 + 排序 + 状态 + 描述 + 操作 + + + + {loading ? ( + + +
+ + 加载中... +
+
+
+ ) : data?.content && data.content.length > 0 ? ( + data.content.map((record) => ( + + + {record.name} + + + + {record.code} + + + + {record.icon ? ( +
+ +
+ ) : ( + - + )} +
+ {record.sort} + + {record.enabled ? ( + + ) : ( + + )} + + + {record.description || '-'} + + +
+ + +
+
+
+ )) + ) : ( + + +
+ +

暂无分类

+
+
+
+ )} +
+
+
+ + {/* 分页 */} + {data && data.totalElements > 0 && ( +
+ setPageNum(page - 1)} + /> +
+ )} + + ) : ( +
+ +
+ ( + + 分类名称 + + + + + + )} + /> + + ( + + 分类代码 + + + + + + )} + /> + + ( + + 图标 + +
+ setIconSelectOpen(true)} + className="cursor-pointer" + /> + {field.value && ( +
+ +
+ )} +
+
+ + +
+ )} + /> + + ( + + 排序 + + field.onChange(Number(e.target.value))} + /> + + + + )} + /> +
+ + ( + + 描述 + +