This commit is contained in:
asp_ly 2024-12-28 21:04:42 +08:00
parent 56523ddf01
commit 244989c3ee
7 changed files with 625 additions and 10 deletions

View File

@ -116,8 +116,8 @@ function generateModal(options: GenerateOptions): string {
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
return `import React, { useEffect } from 'react';
import type { ${typeName}Response } from '@/pages/${name}/List/types';
import { create${typeName}, update${typeName} } from '@/pages/${name}/List/service';
import type { ${typeName}Response } from '../types';
import { create${typeName}, update${typeName} } from '../service';
import {
Dialog,
DialogContent,
@ -139,7 +139,7 @@ import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { ${typeName.toLowerCase()}FormSchema, type ${typeName}FormValues } from "@/pages/${name}/List/schema";
import { ${typeName.toLowerCase()}FormSchema, type ${typeName}FormValues } from '../schema';
import { Textarea } from "@/components/ui/textarea";
interface ${typeName}ModalProps {
@ -347,11 +347,11 @@ import {
} from "@/components/ui/alert-dialog";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { searchFormSchema, type SearchFormValues } from "@/pages/${name}/List/schema";
import { searchFormSchema, type SearchFormValues } from './schema';
import { DataTablePagination } from "@/components/ui/pagination";
import type { ${typeName}Response, ${typeName}Query } from '@/pages/${name}/List/types';
import { get${typeName}Page, delete${typeName} } from '@/pages/${name}/List/service';
import ${typeName}Modal from '@/pages/${name}/List/components/${typeName}Modal';
import type { ${typeName}Response, ${typeName}Query } from './types';
import { get${typeName}Page, delete${typeName} } from './service';
import ${typeName}Modal from './components/${typeName}Modal';
interface Column {
accessorKey?: keyof ${typeName}Response;

View File

@ -0,0 +1,183 @@
import React, { useEffect } from 'react';
import type { JenkinsManagerResponse } from '../types';
import { createJenkinsManager, updateJenkinsManager } from '../service';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/components/ui/use-toast";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { jenkinsmanagerFormSchema, type JenkinsManagerFormValues } from "../schema";
import { Textarea } from "@/components/ui/textarea";
interface JenkinsManagerModalProps {
open: boolean;
onCancel: () => void;
onSuccess: () => void;
initialValues?: JenkinsManagerResponse;
}
const JenkinsManagerModal: React.FC<JenkinsManagerModalProps> = ({
open,
onCancel,
onSuccess,
initialValues,
}) => {
const { toast } = useToast();
const isEdit = !!initialValues?.id;
const form = useForm<JenkinsManagerFormValues>({
resolver: zodResolver(jenkinsmanagerFormSchema),
defaultValues: {
name: "",
description: "",
enabled: true,
sort: 0,
},
});
useEffect(() => {
if (initialValues) {
form.reset({
name: initialValues.name,
description: initialValues.description || "",
enabled: initialValues.enabled,
sort: initialValues.sort,
});
}
}, [initialValues, form]);
const handleSubmit = async (values: JenkinsManagerFormValues) => {
try {
if (isEdit) {
await updateJenkinsManager(initialValues.id, values);
} else {
await createJenkinsManager(values);
}
toast({
title: `${isEdit ? '更新' : '创建'}成功`,
duration: 3000,
});
form.reset();
onSuccess();
} catch (error) {
toast({
variant: "destructive",
title: `${isEdit ? '更新' : '创建'}失败`,
description: error instanceof Error ? error.message : undefined,
duration: 3000,
});
}
};
return (
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>{isEdit ? '编辑' : '新建'}</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({field}) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
{...field}
placeholder="请输入名称"
/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({field}) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Textarea
{...field}
placeholder="请输入描述"
rows={4}
/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="enabled"
render={({field}) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel></FormLabel>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="sort"
render={({field}) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
{...field}
type="number"
min={0}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<DialogFooter>
<Button type="button" variant="outline" onClick={onCancel}>
</Button>
<Button type="submit">
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
export default JenkinsManagerModal;

View File

@ -0,0 +1,360 @@
import React, { useState, useEffect } from 'react';
import { PageContainer } from '@/components/ui/page-container';
import { Plus, Pencil, Trash2, Loader2 } from 'lucide-react';
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from "@/components/ui/table";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { useToast } from "@/components/ui/use-toast";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { searchFormSchema, type SearchFormValues } from "./schema";
import { DataTablePagination } from "@/components/ui/pagination";
import type { JenkinsManagerResponse, JenkinsManagerQuery } from './types';
import { getJenkinsManagerPage, deleteJenkinsManager } from './service';
import JenkinsManagerModal from './components/JenkinsManagerModal';
interface Column {
accessorKey?: keyof JenkinsManagerResponse;
id?: string;
header: string;
size: number;
cell?: (props: { row: { original: JenkinsManagerResponse } }) => React.ReactNode;
}
const JenkinsManagerList: React.FC = () => {
const [modalVisible, setModalVisible] = useState(false);
const [currentRecord, setCurrentRecord] = useState<JenkinsManagerResponse>();
const [list, setList] = useState<JenkinsManagerResponse[]>([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
pageNum: 1,
pageSize: 10,
totalElements: 0,
});
const { toast } = useToast();
const form = useForm<SearchFormValues>({
resolver: zodResolver(searchFormSchema),
defaultValues: {
name: "",
enabled: undefined,
}
});
const loadData = async (params?: JenkinsManagerQuery) => {
setLoading(true);
try {
const queryParams: JenkinsManagerQuery = {
...params,
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
};
const data = await getJenkinsManagerPage(queryParams);
setList(data.content || []);
setPagination({
...pagination,
totalElements: data.totalElements,
});
} catch (error) {
toast({
variant: "destructive",
title: "获取列表失败",
duration: 3000,
});
} finally {
setLoading(false);
}
};
const handlePageChange = (page: number) => {
setPagination({
...pagination,
pageNum: page,
});
};
useEffect(() => {
loadData(form.getValues());
}, [pagination.pageNum, pagination.pageSize]);
const handleDelete = async (id: number) => {
try {
await deleteJenkinsManager(id);
toast({
title: "删除成功",
duration: 3000,
});
loadData(form.getValues());
} catch (error) {
toast({
variant: "destructive",
title: "删除失败",
duration: 3000,
});
}
};
const handleAdd = () => {
setCurrentRecord(undefined);
setModalVisible(true);
};
const handleEdit = (record: JenkinsManagerResponse) => {
setCurrentRecord(record);
setModalVisible(true);
};
const handleModalClose = () => {
setModalVisible(false);
setCurrentRecord(undefined);
};
const handleSuccess = () => {
setModalVisible(false);
setCurrentRecord(undefined);
loadData(form.getValues());
};
const handleReset = () => {
form.reset({
name: "",
enabled: undefined,
});
loadData();
};
const columns: Column[] = [
{
accessorKey: 'name',
header: '名称',
size: 180,
},
{
accessorKey: 'description',
header: '描述',
size: 200,
},
{
accessorKey: 'enabled',
header: '状态',
size: 100,
cell: ({row}) => (
<Badge variant={row.original.enabled ? "outline" : "secondary"}>
{row.original.enabled ? '启用' : '禁用'}
</Badge>
),
},
{
accessorKey: 'sort',
header: '排序',
size: 80,
},
{
accessorKey: 'createTime',
header: '创建时间',
size: 180,
},
{
id: 'actions',
header: '操作',
size: 180,
cell: ({row}) => (
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => handleEdit(row.original)}
>
<Pencil className="mr-2 h-4 w-4" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="sm"
className="text-destructive"
>
<Trash2 className="mr-2 h-4 w-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(row.original.id)}
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
),
},
];
return (
<PageContainer>
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight">Jenkins管理</h2>
<div className="flex items-center gap-4">
<Button onClick={handleAdd}>
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
<Card>
<div className="p-6">
<div className="flex items-center gap-4">
<Input
placeholder="名称"
{...form.register('name')}
className="max-w-[200px]"
/>
<Select
value={form.getValues('enabled')?.toString()}
onValueChange={(value) => form.setValue('enabled', value === 'true')}
>
<SelectTrigger className="max-w-[200px]">
<SelectValue placeholder="状态"/>
</SelectTrigger>
<SelectContent>
<SelectItem value="true"></SelectItem>
<SelectItem value="false"></SelectItem>
</SelectContent>
</Select>
<Button variant="outline" onClick={handleReset}>
</Button>
<Button onClick={() => loadData(form.getValues())}>
</Button>
</div>
</div>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
{columns.map((column) => (
<TableHead
key={column.accessorKey || column.id}
style={{width: column.size}}
>
{column.header}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
<div className="flex justify-center items-center">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
</div>
</TableCell>
</TableRow>
) : list.length === 0 ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
</TableCell>
</TableRow>
) : (
list.map((item) => (
<TableRow key={item.id}>
{columns.map((column) => (
<TableCell
key={column.accessorKey || column.id}
>
{column.cell
? column.cell({row: {original: item}})
: item[column.accessorKey!]}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
<div className="flex justify-end border-t border-border bg-muted/40">
<DataTablePagination
pageIndex={pagination.pageNum}
pageSize={pagination.pageSize}
pageCount={Math.ceil(pagination.totalElements / pagination.pageSize)}
onPageChange={handlePageChange}
/>
</div>
</div>
</CardContent>
</Card>
{modalVisible && (
<JenkinsManagerModal
open={modalVisible}
onCancel={handleModalClose}
onSuccess={handleSuccess}
initialValues={currentRecord}
/>
)}
</PageContainer>
);
};
export default JenkinsManagerList;

View File

@ -0,0 +1,16 @@
import * as z from "zod";
export const searchFormSchema = z.object({
name: z.string().optional(),
enabled: z.boolean().optional(),
});
export const jenkinsmanagerFormSchema = z.object({
name: z.string().min(1, "请输入名称").max(50, "名称不能超过50个字符"),
description: z.string().max(200, "描述不能超过200个字符").optional(),
enabled: z.boolean().default(true),
sort: z.number().min(0).default(0),
});
export type SearchFormValues = z.infer<typeof searchFormSchema>;
export type JenkinsManagerFormValues = z.infer<typeof jenkinsmanagerFormSchema>;

View File

@ -0,0 +1,33 @@
import request from '@/utils/request';
import type { Page } from '@/types/base';
import type { JenkinsManagerResponse, JenkinsManagerQuery, JenkinsManagerRequest } from './types';
const BASE_URL = '/api/v1/jenkins-manager';
// 创建
export const createJenkinsManager = (data: JenkinsManagerRequest) =>
request.post<void>(BASE_URL, data);
// 更新
export const updateJenkinsManager = (id: number, data: JenkinsManagerRequest) =>
request.put<void>(`${BASE_URL}/${id}`, data);
// 删除
export const deleteJenkinsManager = (id: number) =>
request.delete<void>(`${BASE_URL}/${id}`);
// 获取详情
export const getJenkinsManager = (id: number) =>
request.get<JenkinsManagerResponse>(`${BASE_URL}/${id}`);
// 分页查询列表
export const getJenkinsManagerPage = (params?: JenkinsManagerQuery) =>
request.get<Page<JenkinsManagerResponse>>(`${BASE_URL}/page`, { params });
// 获取所有列表
export const getJenkinsManagerList = () =>
request.get<JenkinsManagerResponse[]>(BASE_URL);
// 条件查询列表
export const getJenkinsManagerListByCondition = (params?: JenkinsManagerQuery) =>
request.get<JenkinsManagerResponse[]>(`${BASE_URL}/list`, { params });

View File

@ -0,0 +1,23 @@
import type { BaseResponse, BaseQuery } from '@/types/base';
export interface JenkinsManagerResponse extends BaseResponse {
name: string;
description?: string;
enabled: boolean;
sort: number;
// TODO: 添加其他字段
}
export interface JenkinsManagerQuery extends BaseQuery {
name?: string;
enabled?: boolean;
// TODO: 添加其他查询字段
}
export interface JenkinsManagerRequest {
name: string;
description?: string;
enabled: boolean;
sort: number;
// TODO: 添加其他请求字段
}

View File

@ -42,7 +42,7 @@ const ProjectGroupList = lazy(() => import('../pages/Deploy/ProjectGroup/List'))
const ApplicationList = lazy(() => import('../pages/Deploy/Application/List'));
const EnvironmentList = lazy(() => import('../pages/Deploy/Environment/List'));
const DeploymentConfigList = lazy(() => import('../pages/Deploy/Deployment/List'));
const JenkinsList = lazy(() => import('../pages/Jenkins/List'));
const JenkinsManagerList = lazy(() => import('../pages/Deploy/JenkinsManager/List'));
const External = lazy(() => import('../pages/Deploy/External'));
// 创建路由
@ -91,8 +91,8 @@ const router = createBrowserRouter([
element: <Suspense fallback={<LoadingComponent/>}><DeploymentConfigList/></Suspense>
},
{
path: 'jenkins',
element: <Suspense fallback={<LoadingComponent/>}><JenkinsList/></Suspense>
path: 'jenkins-manager',
element: <Suspense fallback={<LoadingComponent/>}><JenkinsManagerList/></Suspense>
},
{
path: 'external',