This commit is contained in:
asp_ly 2024-12-27 23:47:10 +08:00
parent 5eb44c62cb
commit c2c0e3c815
4 changed files with 584 additions and 316 deletions

View File

@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
})
Textarea.displayName = "Textarea"
export { Textarea }

View File

@ -1,8 +1,37 @@
import React, {useState} from 'react'; import React, {useEffect} from 'react';
import {Modal, Form, Input, Select, Switch, InputNumber, message} from 'antd';
import type {Application} from '../types'; import type {Application} from '../types';
import {DevelopmentLanguageTypeEnum} from '../types'; import {DevelopmentLanguageTypeEnum} from '../types';
import {createApplication, updateApplication} from '../service'; import {createApplication, updateApplication} 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 {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
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 {applicationFormSchema, type ApplicationFormValues} from "../schema";
import {Textarea} from "@/components/ui/textarea";
interface ApplicationModalProps { interface ApplicationModalProps {
open: boolean; open: boolean;
@ -19,18 +48,43 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
initialValues, initialValues,
projectGroupId, projectGroupId,
}) => { }) => {
const [form] = Form.useForm(); const {toast} = useToast();
const [loading, setLoading] = useState(false);
const isEdit = !!initialValues?.id; const isEdit = !!initialValues?.id;
const handleSubmit = async () => { const form = useForm<ApplicationFormValues>({
resolver: zodResolver(applicationFormSchema),
defaultValues: {
appCode: "",
appName: "",
appDesc: "",
repoUrl: "",
language: undefined,
enabled: true,
sort: 0,
},
});
useEffect(() => {
if (initialValues) {
form.reset({
appCode: initialValues.appCode,
appName: initialValues.appName,
appDesc: initialValues.appDesc || "",
repoUrl: initialValues.repoUrl,
language: initialValues.language,
enabled: initialValues.enabled,
sort: initialValues.sort,
});
}
}, [initialValues, form]);
const handleSubmit = async (values: ApplicationFormValues) => {
try { try {
setLoading(true);
const values = await form.validateFields();
if (isEdit) { if (isEdit) {
await updateApplication({ await updateApplication({
...values, ...values,
id: initialValues.id, id: initialValues.id,
projectGroupId,
}); });
} else { } else {
await createApplication({ await createApplication({
@ -38,136 +92,177 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
projectGroupId, projectGroupId,
}); });
} }
message.success(`${isEdit ? '更新' : '创建'}成功`); toast({
form.resetFields(); title: `${isEdit ? '更新' : '创建'}成功`,
duration: 3000,
});
form.reset();
onSuccess(); onSuccess();
} catch (error) { } catch (error) {
if (error instanceof Error) { toast({
message.error(`${isEdit ? '更新' : '创建'}失败: ${error.message}`); variant: "destructive",
} else { title: `${isEdit ? '更新' : '创建'}失败`,
message.error(`${isEdit ? '更新' : '创建'}失败`); description: error instanceof Error ? error.message : undefined,
} duration: 3000,
} finally { });
setLoading(false);
} }
}; };
return ( return (
<Modal <Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
title={`${isEdit ? '编辑' : '新建'}应用`} <DialogContent className="sm:max-w-[600px]">
open={open} <DialogHeader>
onCancel={() => { <DialogTitle>{isEdit ? '编辑' : '新建'}</DialogTitle>
form.resetFields(); </DialogHeader>
onCancel(); <Form {...form}>
}} <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
onOk={handleSubmit} <FormField
confirmLoading={loading} control={form.control}
maskClosable={false} name="appCode"
destroyOnClose render={({field}) => (
> <FormItem>
<Form <FormLabel></FormLabel>
form={form} <FormControl>
layout="vertical" <Input
initialValues={{ {...field}
enabled: true, disabled={isEdit}
sort: 0, placeholder="请输入应用编码"
...initialValues, />
}} </FormControl>
> <FormMessage/>
<Form.Item </FormItem>
name="appCode" )}
label="应用编码" />
rules={[
{required: true, message: '请输入应用编码'},
{max: 50, message: '应用编码不能超过50个字符'},
]}
tooltip="应用的唯一标识,创建后不可修改"
>
<Input
placeholder="请输入应用编码"
disabled={isEdit}
maxLength={50}
/>
</Form.Item>
<Form.Item <FormField
name="appName" control={form.control}
label="应用名称" name="appName"
rules={[ render={({field}) => (
{required: true, message: '请输入应用名称'}, <FormItem>
{max: 50, message: '应用名称不能超过50个字符'}, <FormLabel></FormLabel>
]} <FormControl>
tooltip="应用的显示名称" <Input
> {...field}
<Input placeholder="请输入应用名称"
placeholder="请输入应用名称" />
maxLength={50} </FormControl>
/> <FormMessage/>
</Form.Item> </FormItem>
)}
/>
<Form.Item <FormField
name="language" control={form.control}
label="开发语言" name="language"
rules={[{required: true, message: '请选择开发语言'}]} render={({field}) => (
tooltip="应用的主要开发语言,创建后不可修改" <FormItem>
> <FormLabel></FormLabel>
<Select <Select
placeholder="请选择开发语言" disabled={isEdit}
disabled={isEdit} onValueChange={field.onChange}
> value={field.value}
<Select.Option value={DevelopmentLanguageTypeEnum.JAVA}>Java</Select.Option> >
<Select.Option value={DevelopmentLanguageTypeEnum.NODE_JS}>NodeJS</Select.Option> <FormControl>
<Select.Option value={DevelopmentLanguageTypeEnum.PYTHON}>Python</Select.Option> <SelectTrigger>
<Select.Option value={DevelopmentLanguageTypeEnum.GO}>Go</Select.Option> <SelectValue placeholder="请选择开发语言"/>
</Select> </SelectTrigger>
</Form.Item> </FormControl>
<SelectContent>
<SelectItem value={DevelopmentLanguageTypeEnum.JAVA}>Java</SelectItem>
<SelectItem value={DevelopmentLanguageTypeEnum.NODE_JS}>NodeJS</SelectItem>
<SelectItem value={DevelopmentLanguageTypeEnum.PYTHON}>Python</SelectItem>
<SelectItem value={DevelopmentLanguageTypeEnum.GO}>Go</SelectItem>
</SelectContent>
</Select>
<FormMessage/>
</FormItem>
)}
/>
<Form.Item <FormField
name="repoUrl" control={form.control}
label="仓库地址" name="repoUrl"
rules={[ render={({field}) => (
{required: true, message: '请输入仓库地址'}, <FormItem>
{type: 'url', message: '请输入有效的URL地址'}, <FormLabel></FormLabel>
]} <FormControl>
tooltip="应用代码仓库的URL地址" <Input
> {...field}
<Input placeholder="请输入仓库地址"
placeholder="请输入仓库地址" />
/> </FormControl>
</Form.Item> <FormMessage/>
</FormItem>
)}
/>
<Form.Item <FormField
name="appDesc" control={form.control}
label="应用描述" name="appDesc"
rules={[{max: 200, message: '应用描述不能超过200个字符'}]} render={({field}) => (
tooltip="应用的详细描述信息" <FormItem>
> <FormLabel></FormLabel>
<Input.TextArea <FormControl>
placeholder="请输入应用描述" <Textarea
maxLength={200} {...field}
showCount placeholder="请输入应用描述"
rows={4} rows={4}
/> />
</Form.Item> </FormControl>
<FormMessage/>
</FormItem>
)}
/>
<Form.Item <FormField
name="enabled" control={form.control}
label="状态" name="enabled"
valuePropName="checked" render={({field}) => (
tooltip="是否启用该应用" <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
> <div className="space-y-0.5">
<Switch checkedChildren="启用" unCheckedChildren="禁用"/> <FormLabel></FormLabel>
</Form.Item> </div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<Form.Item <FormField
name="sort" control={form.control}
label="排序" name="sort"
tooltip="数字越小越靠前" render={({field}) => (
> <FormItem>
<InputNumber min={0} style={{width: '100%'}}/> <FormLabel></FormLabel>
</Form.Item> <FormControl>
</Form> <Input
</Modal> {...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>
); );
}; };

View File

@ -1,6 +1,5 @@
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect} from 'react';
import {PageContainer} from '@ant-design/pro-layout'; import {PageContainer} from '@/components/ui/page-container';
import {Button, Space, Popconfirm, Tag, Select, App} from 'antd';
import { import {
PlusOutlined, PlusOutlined,
EditOutlined, EditOutlined,
@ -19,18 +18,79 @@ import type {ProjectGroup} from '../../ProjectGroup/List/types';
import {ProjectGroupTypeEnum} from '../../ProjectGroup/List/types'; import {ProjectGroupTypeEnum} from '../../ProjectGroup/List/types';
import {getProjectTypeInfo} from '../../ProjectGroup/List/utils'; import {getProjectTypeInfo} from '../../ProjectGroup/List/utils';
import ApplicationModal from './components/ApplicationModal'; import ApplicationModal from './components/ApplicationModal';
import {ProTable} from '@ant-design/pro-components'; import {
import type {ProColumns, ActionType} from '@ant-design/pro-components'; 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 {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form";
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";
const {Option} = Select; interface Column {
accessorKey?: keyof Application;
id?: string;
header: string;
size: number;
cell?: (props: { row: { original: Application } }) => React.ReactNode;
}
const ApplicationList: React.FC = () => { const ApplicationList: React.FC = () => {
const [projectGroups, setProjects] = useState<ProjectGroup[]>([]); const [projectGroups, setProjects] = useState<ProjectGroup[]>([]);
const [selectedProjectGroupId, setSelectedProjectGroupId] = useState<number>(); const [selectedProjectGroupId, setSelectedProjectGroupId] = useState<number>();
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const [currentApplication, setCurrentApplication] = useState<Application>(); const [currentApplication, setCurrentApplication] = useState<Application>();
const actionRef = React.useRef<ActionType>(); const [list, setList] = useState<Application[]>([]);
const {message: messageApi} = App.useApp(); const [loading, setLoading] = useState(false);
const {toast} = useToast();
const form = useForm<SearchFormValues>({
resolver: zodResolver(searchFormSchema),
defaultValues: {
appCode: "",
appName: "",
language: undefined,
enabled: undefined,
},
});
// 获取项目列表 // 获取项目列表
const fetchProjects = async () => { const fetchProjects = async () => {
@ -41,7 +101,11 @@ const ApplicationList: React.FC = () => {
setSelectedProjectGroupId(data[0].id); setSelectedProjectGroupId(data[0].id);
} }
} catch (error) { } catch (error) {
messageApi.error('获取项目组列表失败'); toast({
variant: "destructive",
title: "获取项目组列表失败",
duration: 3000,
});
} }
}; };
@ -49,19 +113,58 @@ const ApplicationList: React.FC = () => {
fetchProjects(); fetchProjects();
}, []); }, []);
const loadData = async (params?: ApplicationQuery) => {
if (!selectedProjectGroupId) {
setList([]);
return;
}
setLoading(true);
try {
const queryParams: ApplicationQuery = {
...params,
projectGroupId: selectedProjectGroupId,
};
const data = await getApplicationPage(queryParams);
setList(data.content || []);
} catch (error) {
toast({
variant: "destructive",
title: "获取应用列表失败",
duration: 3000,
});
} finally {
setLoading(false);
}
};
useEffect(() => {
loadData(form.getValues());
}, [selectedProjectGroupId]);
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
await deleteApplication(id); await deleteApplication(id);
messageApi.success('删除成功'); toast({
actionRef.current?.reload(); title: "删除成功",
duration: 3000,
});
loadData(form.getValues());
} catch (error) { } catch (error) {
messageApi.error('删除失败'); toast({
variant: "destructive",
title: "删除失败",
duration: 3000,
});
} }
}; };
const handleAdd = () => { const handleAdd = () => {
if (!selectedProjectGroupId) { if (!selectedProjectGroupId) {
messageApi.warning('请先选择项目组'); toast({
variant: "destructive",
title: "请先选择项目组",
duration: 3000,
});
return; return;
} }
setCurrentApplication(undefined); setCurrentApplication(undefined);
@ -73,9 +176,8 @@ const ApplicationList: React.FC = () => {
setModalVisible(true); setModalVisible(true);
}; };
const handleProjectChange = (value: number) => { const handleProjectChange = (value: string) => {
setSelectedProjectGroupId(value); setSelectedProjectGroupId(Number(value));
actionRef.current?.reload();
}; };
const handleModalClose = () => { const handleModalClose = () => {
@ -86,7 +188,7 @@ const ApplicationList: React.FC = () => {
const handleSuccess = () => { const handleSuccess = () => {
setModalVisible(false); setModalVisible(false);
setCurrentApplication(undefined); setCurrentApplication(undefined);
actionRef.current?.reload(); loadData(form.getValues());
}; };
// 获取开发语言信息 // 获取开发语言信息
@ -125,217 +227,242 @@ const ApplicationList: React.FC = () => {
} }
}; };
const columns: ProColumns<Application>[] = [ const columns: Column[] = [
{ {
title: '应用编码', accessorKey: 'appCode',
dataIndex: 'appCode', header: '应用编码',
width: 180, size: 180,
copyable: true,
ellipsis: true,
fixed: 'left',
}, },
{ {
title: '应用名称', accessorKey: 'appName',
dataIndex: 'appName', header: '应用名称',
width: 150, size: 150,
ellipsis: true,
}, },
{ {
title: '项目组', id: 'projectGroup',
dataIndex: ['projectGroup', 'projectGroupName'], header: '项目组',
width: 150, size: 150,
ellipsis: true, cell: ({row}) => (
render: (_, record) => ( <div className="flex items-center gap-2">
<Space> <span>{row.original.projectGroup?.projectGroupName}</span>
{record.projectGroup?.projectGroupName} {row.original.projectGroup?.type && (
{record.projectGroup?.type && ( <Badge variant="outline">
<Tag color={getProjectTypeInfo(record.projectGroup.type).color}> {getProjectTypeInfo(row.original.projectGroup.type).label}
{getProjectTypeInfo(record.projectGroup.type).label} </Badge>
</Tag>
)} )}
</Space> </div>
), ),
}, },
{ {
title: '应用描述', accessorKey: 'appDesc',
dataIndex: 'appDesc', header: '应用描述',
ellipsis: true, size: 200,
width: 200,
}, },
{ {
title: '仓库地址', accessorKey: 'repoUrl',
dataIndex: 'repoUrl', header: '仓库地址',
width: 200, size: 200,
ellipsis: true, cell: ({row}) => row.original.repoUrl ? (
render: (_, record) => record.repoUrl ? ( <div className="flex items-center gap-2">
<Space>
<GithubOutlined/> <GithubOutlined/>
<a href={record.repoUrl} target="_blank" rel="noopener noreferrer"> <a href={row.original.repoUrl} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-700">
{record.repoUrl} {row.original.repoUrl}
</a> </a>
</Space> </div>
) : '-', ) : '-',
}, },
{ {
title: '开发语言', accessorKey: 'language',
dataIndex: 'language', header: '开发语言',
width: 120, size: 120,
render: (language) => { cell: ({row}) => {
const langInfo = getLanguageInfo(language as DevelopmentLanguageTypeEnum); const langInfo = getLanguageInfo(row.original.language);
return ( return (
<Tag color={langInfo.color}> <Badge variant="outline" className="flex items-center gap-1">
<Space> {langInfo.icon}
{langInfo.icon} {langInfo.label}
{langInfo.label} </Badge>
</Space>
</Tag>
); );
}, },
filters: [
{text: 'Java', value: DevelopmentLanguageTypeEnum.JAVA},
{text: 'NodeJS', value: DevelopmentLanguageTypeEnum.NODE_JS},
{text: 'Python', value: DevelopmentLanguageTypeEnum.PYTHON},
{text: 'Go', value: DevelopmentLanguageTypeEnum.GO},
],
filterMode: 'menu',
filtered: false,
}, },
{ {
title: '状态', accessorKey: 'enabled',
dataIndex: 'enabled', header: '状态',
width: 100, size: 100,
valueEnum: { cell: ({row}) => (
true: {text: '启用', status: 'Success'}, <Badge variant={row.original.enabled ? "outline" : "secondary"}>
false: {text: '禁用', status: 'Default'}, {row.original.enabled ? '启用' : '禁用'}
}, </Badge>
),
}, },
{ {
title: '排序', accessorKey: 'sort',
dataIndex: 'sort', header: '排序',
width: 80, size: 80,
sorter: true,
}, },
{ {
title: '操作', id: 'actions',
width: 180, header: '操作',
key: 'action', size: 180,
valueType: 'option', cell: ({row}) => (
fixed: 'right', <div className="flex items-center gap-2">
render: (_, record) => [
<Button
key="edit"
type="link"
onClick={() => handleEdit(record)}
>
<Space>
<EditOutlined/>
</Space>
</Button>,
<Popconfirm
key="delete"
title="确定要删除该应用吗?"
description="删除后将无法恢复,请谨慎操作"
onConfirm={() => handleDelete(record.id)}
>
<Button <Button
type="link" variant="ghost"
danger size="sm"
onClick={() => handleEdit(row.original)}
> >
<Space> <EditOutlined className="mr-1"/>
<DeleteOutlined/>
</Space>
</Button> </Button>
</Popconfirm> <AlertDialog>
], <AlertDialogTrigger asChild>
<Button
variant="ghost"
size="sm"
className="text-destructive"
>
<DeleteOutlined className="mr-1"/>
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(row.original.id)}
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
),
}, },
]; ];
return ( return (
<PageContainer <PageContainer>
header={{ <div className="flex items-center justify-between">
title: '应用管理', <h2 className="text-3xl font-bold tracking-tight"></h2>
extra: [ <div className="flex items-center gap-4">
<Select <Select
key="project-select" value={selectedProjectGroupId?.toString()}
value={selectedProjectGroupId} onValueChange={handleProjectChange}
onChange={handleProjectChange}
style={{width: 200}}
placeholder="请选择项目组"
> >
{projectGroups.map((project) => ( <SelectTrigger className="w-[200px]">
<Option key={project.id} value={project.id}> <SelectValue placeholder="请选择项目组"/>
{project.projectGroupName} </SelectTrigger>
</Option> <SelectContent>
))} {projectGroups.map((project) => (
</Select>, <SelectItem key={project.id} value={project.id.toString()}>
], {project.projectGroupName}
}} </SelectItem>
> ))}
<ProTable<Application> </SelectContent>
columns={columns} </Select>
actionRef={actionRef} <Button onClick={handleAdd} disabled={!selectedProjectGroupId}>
scroll={{x: 'max-content'}} <PlusOutlined className="mr-1"/>
cardBordered
rowKey="id" </Button>
search={false} </div>
options={{ </div>
setting: false,
density: false, <Card>
fullScreen: false, <div className="p-6">
reload: false, <div className="flex items-center gap-4">
}} <Input
toolbar={{ placeholder="应用编码"
actions: [ value={form.getValues('appCode')}
<Button onChange={(e) => form.setValue('appCode', e.target.value)}
key="add" className="max-w-[200px]"
type="primary" />
onClick={handleAdd} <Input
icon={<PlusOutlined/>} placeholder="应用名称"
disabled={!selectedProjectGroupId} value={form.getValues('appName')}
onChange={(e) => form.setValue('appName', e.target.value)}
className="max-w-[200px]"
/>
<Select
value={form.getValues('language')}
onValueChange={(value) => form.setValue('language', value as DevelopmentLanguageTypeEnum)}
> >
<SelectTrigger className="max-w-[200px]">
<SelectValue placeholder="开发语言"/>
</SelectTrigger>
<SelectContent>
<SelectItem value={DevelopmentLanguageTypeEnum.JAVA}>Java</SelectItem>
<SelectItem value={DevelopmentLanguageTypeEnum.NODE_JS}>NodeJS</SelectItem>
<SelectItem value={DevelopmentLanguageTypeEnum.PYTHON}>Python</SelectItem>
<SelectItem value={DevelopmentLanguageTypeEnum.GO}>Go</SelectItem>
</SelectContent>
</Select>
<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={() => form.reset()}>
</Button> </Button>
], <Button variant="ghost" onClick={() => loadData(form.getValues())}>
}}
pagination={{ </Button>
pageSize: 10, </div>
showQuickJumper: true, </div>
}} </Card>
request={async (params) => {
if (!selectedProjectGroupId) { <Card>
return { <CardHeader className="flex flex-row items-center justify-between">
data: [], <CardTitle></CardTitle>
success: true, </CardHeader>
total: 0, <CardContent>
}; <div className="rounded-md border">
} <Table>
try { <TableHeader>
const queryParams: ApplicationQuery = { <TableRow>
pageSize: params.pageSize, {columns.map((column) => (
pageNum: params.current, <TableHead
projectGroupId: selectedProjectGroupId, key={column.accessorKey || column.id}
appCode: params.appCode as string, style={{width: column.size}}
appName: params.appName as string, >
enabled: params.enabled as boolean, {column.header}
}; </TableHead>
const data = await getApplicationPage(queryParams); ))}
return { </TableRow>
data: data.content || [], </TableHeader>
success: true, <TableBody>
total: data.totalElements || 0, {list.map((item) => (
}; <TableRow key={item.id}>
} catch (error) { {columns.map((column) => (
messageApi.error('获取应用列表失败'); <TableCell
return { key={column.accessorKey || column.id}
data: [], >
success: false, {column.cell
total: 0, ? column.cell({row: {original: item}})
}; : item[column.accessorKey!]}
} </TableCell>
}} ))}
/> </TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
{modalVisible && selectedProjectGroupId && ( {modalVisible && selectedProjectGroupId && (
<ApplicationModal <ApplicationModal

View File

@ -0,0 +1,24 @@
import * as z from "zod";
import { DevelopmentLanguageTypeEnum } from "./types";
export const searchFormSchema = z.object({
appCode: z.string().optional(),
appName: z.string().optional(),
language: z.nativeEnum(DevelopmentLanguageTypeEnum).optional(),
enabled: z.boolean().optional(),
});
export const applicationFormSchema = z.object({
appCode: z.string().min(1, "请输入应用编码").max(50, "应用编码不能超过50个字符"),
appName: z.string().min(1, "请输入应用名称").max(50, "应用名称不能超过50个字符"),
appDesc: z.string().max(200, "应用描述不能超过200个字符").optional(),
repoUrl: z.string().url("请输入有效的URL地址").min(1, "请输入仓库地址"),
language: z.nativeEnum(DevelopmentLanguageTypeEnum, {
required_error: "请选择开发语言",
}),
enabled: z.boolean().default(true),
sort: z.number().min(0).default(0),
});
export type SearchFormValues = z.infer<typeof searchFormSchema>;
export type ApplicationFormValues = z.infer<typeof applicationFormSchema>;