增加团队管理页面
This commit is contained in:
parent
bd984da237
commit
e4cabb125a
@ -64,7 +64,7 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
"fixed left-[50%] top-[50%] z-50 flex flex-col w-full max-w-lg max-h-[90vh] translate-x-[-50%] translate-y-[-50%] border bg-background shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -85,7 +85,7 @@ const DialogHeader = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
"flex flex-col space-y-1.5 text-center sm:text-left flex-shrink-0 px-6 pt-6",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -93,13 +93,27 @@ const DialogHeader = ({
|
|||||||
)
|
)
|
||||||
DialogHeader.displayName = "DialogHeader"
|
DialogHeader.displayName = "DialogHeader"
|
||||||
|
|
||||||
|
const DialogBody = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex-1 overflow-y-auto px-6 py-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogBody.displayName = "DialogBody"
|
||||||
|
|
||||||
const DialogFooter = ({
|
const DialogFooter = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 flex-shrink-0 px-6 pb-6 pt-4 border-t",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -142,6 +156,7 @@ export {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
|
|||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary/20 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
@ -234,7 +235,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
<DialogContent className="max-w-6xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<FolderKanban className="h-5 w-5" />
|
<FolderKanban className="h-5 w-5" />
|
||||||
@ -242,6 +243,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
{!editMode ? (
|
{!editMode ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* 搜索栏 */}
|
{/* 搜索栏 */}
|
||||||
@ -495,18 +497,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
|
||||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button type="submit">
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
|
{editMode && (
|
||||||
|
<div className="flex items-center justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||||
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={form.handleSubmit(handleSave)}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
@ -92,11 +93,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{record ? '编辑环境' : '新建环境'}</DialogTitle>
|
<DialogTitle>{record ? '编辑环境' : '新建环境'}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-4">
|
<DialogBody>
|
||||||
|
<div className="grid gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="tenantCode">租户代码 *</Label>
|
<Label htmlFor="tenantCode">租户代码 *</Label>
|
||||||
<Input
|
<Input
|
||||||
@ -160,6 +162,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DialogBody>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
取消
|
取消
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
@ -123,11 +124,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{record ? '编辑系统' : '新增系统'}</DialogTitle>
|
<DialogTitle>{record ? '编辑系统' : '新增系统'}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-4">
|
<DialogBody>
|
||||||
|
<div className="grid gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="name">系统名称 *</Label>
|
<Label htmlFor="name">系统名称 *</Label>
|
||||||
<Input
|
<Input
|
||||||
@ -250,6 +252,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DialogBody>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
取消
|
取消
|
||||||
|
|||||||
@ -28,6 +28,8 @@ export interface RepositoryGroupResponse extends BaseResponse {
|
|||||||
repoGroupId?: number;
|
repoGroupId?: number;
|
||||||
/** 外部系统ID */
|
/** 外部系统ID */
|
||||||
externalSystemId: number;
|
externalSystemId: number;
|
||||||
|
/** 项目数量 */
|
||||||
|
projectCount?: number;
|
||||||
/** 子仓库组列表(用于树形结构) */
|
/** 子仓库组列表(用于树形结构) */
|
||||||
children?: RepositoryGroupResponse[];
|
children?: RepositoryGroupResponse[];
|
||||||
}
|
}
|
||||||
@ -60,6 +62,8 @@ export interface RepositoryProjectResponse extends BaseResponse {
|
|||||||
repoGroupId?: number;
|
repoGroupId?: number;
|
||||||
/** 外部系统中的项目ID */
|
/** 外部系统中的项目ID */
|
||||||
repoProjectId?: number;
|
repoProjectId?: number;
|
||||||
|
/** 分支数量 */
|
||||||
|
branchCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import {
|
import {
|
||||||
@ -239,7 +240,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
<DialogContent className="max-w-6xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<FolderKanban className="h-5 w-5" />
|
<FolderKanban className="h-5 w-5" />
|
||||||
@ -247,6 +248,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
{!editMode ? (
|
{!editMode ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* 搜索和新建 */}
|
{/* 搜索和新建 */}
|
||||||
@ -370,8 +372,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
pageIndex={pageNum}
|
pageIndex={pageNum}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
totalPages={data.totalPages}
|
pageCount={data.totalPages}
|
||||||
totalElements={data.totalElements}
|
|
||||||
onPageChange={setPageNum}
|
onPageChange={setPageNum}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -514,18 +515,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 pt-4">
|
|
||||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button type="submit">
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
|
{editMode && (
|
||||||
|
<div className="flex justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||||
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={form.handleSubmit(handleSave)}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,430 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
FormDescription,
|
||||||
|
} from '@/components/ui/form';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
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 { Loader2 } from 'lucide-react';
|
||||||
|
import { createScheduleJob, updateScheduleJob } from '../service';
|
||||||
|
import type { ScheduleJobResponse, ScheduleJobRequest, JobCategoryResponse } from '../types';
|
||||||
|
|
||||||
|
interface JobEditDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
job?: ScheduleJobResponse | null;
|
||||||
|
categories: JobCategoryResponse[];
|
||||||
|
onSuccess?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时任务编辑对话框
|
||||||
|
*/
|
||||||
|
const JobEditDialog: React.FC<JobEditDialogProps> = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
job,
|
||||||
|
categories,
|
||||||
|
onSuccess,
|
||||||
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [submitting, setSubmitting] = React.useState(false);
|
||||||
|
|
||||||
|
const form = useForm<ScheduleJobRequest>({
|
||||||
|
defaultValues: {
|
||||||
|
jobName: '',
|
||||||
|
jobDescription: '',
|
||||||
|
categoryId: undefined,
|
||||||
|
beanName: '',
|
||||||
|
methodName: '',
|
||||||
|
formDefinitionId: undefined,
|
||||||
|
methodParams: '',
|
||||||
|
cronExpression: '',
|
||||||
|
status: 'DISABLED',
|
||||||
|
concurrent: false,
|
||||||
|
timeoutSeconds: 300,
|
||||||
|
retryCount: 0,
|
||||||
|
alertEmail: '',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当对话框打开或job变化时,重置表单
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
if (job) {
|
||||||
|
// 编辑模式
|
||||||
|
form.reset({
|
||||||
|
jobName: job.jobName,
|
||||||
|
jobDescription: job.jobDescription || '',
|
||||||
|
categoryId: job.categoryId,
|
||||||
|
beanName: job.beanName,
|
||||||
|
methodName: job.methodName,
|
||||||
|
formDefinitionId: job.formDefinitionId,
|
||||||
|
methodParams: job.methodParams || '',
|
||||||
|
cronExpression: job.cronExpression,
|
||||||
|
status: job.status,
|
||||||
|
concurrent: job.concurrent,
|
||||||
|
timeoutSeconds: job.timeoutSeconds || 300,
|
||||||
|
retryCount: job.retryCount,
|
||||||
|
alertEmail: job.alertEmail || '',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 新建模式
|
||||||
|
form.reset({
|
||||||
|
jobName: '',
|
||||||
|
jobDescription: '',
|
||||||
|
categoryId: undefined,
|
||||||
|
beanName: '',
|
||||||
|
methodName: '',
|
||||||
|
formDefinitionId: undefined,
|
||||||
|
methodParams: '',
|
||||||
|
cronExpression: '',
|
||||||
|
status: 'DISABLED',
|
||||||
|
concurrent: false,
|
||||||
|
timeoutSeconds: 300,
|
||||||
|
retryCount: 0,
|
||||||
|
alertEmail: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [open, job, form]);
|
||||||
|
|
||||||
|
const handleSubmit = async (values: ScheduleJobRequest) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
if (job) {
|
||||||
|
await updateScheduleJob(job.id, values);
|
||||||
|
toast({
|
||||||
|
title: '更新成功',
|
||||||
|
description: `任务 "${values.jobName}" 已更新`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createScheduleJob(values);
|
||||||
|
toast({
|
||||||
|
title: '创建成功',
|
||||||
|
description: `任务 "${values.jobName}" 已创建`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onOpenChange(false);
|
||||||
|
onSuccess?.();
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
variant: 'destructive',
|
||||||
|
title: job ? '更新失败' : '创建失败',
|
||||||
|
description: error instanceof Error ? error.message : '未知错误',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-3xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{job ? '编辑定时任务' : '新建定时任务'}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSubmit)} className="flex flex-col flex-1 min-h-0">
|
||||||
|
<DialogBody className="space-y-4">
|
||||||
|
{/* 基本信息 */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="jobName"
|
||||||
|
rules={{ required: '请输入任务名称' }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>任务名称 *</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="请输入任务名称" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="categoryId"
|
||||||
|
rules={{ required: '请选择任务分类' }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>任务分类 *</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value?.toString()}
|
||||||
|
onValueChange={(value) => field.onChange(Number(value))}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择任务分类" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<SelectItem key={cat.id} value={cat.id.toString()}>
|
||||||
|
{cat.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="jobDescription"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>任务描述</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="请输入任务描述" rows={2} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 执行配置 */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="beanName"
|
||||||
|
rules={{ required: '请输入Bean名称' }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Bean名称 *</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="例如:myTaskBean" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="methodName"
|
||||||
|
rules={{ required: '请输入方法名称' }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>方法名称 *</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="例如:execute" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="methodParams"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>方法参数</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder='JSON格式,例如:{"key": "value"}'
|
||||||
|
rows={2}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
JSON格式的方法参数(可选)
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 调度配置 */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="cronExpression"
|
||||||
|
rules={{ required: '请输入Cron表达式' }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Cron表达式 *</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="例如:0 0 1 * * ?" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
例如:0 0 1 * * ? (每天凌晨1点)
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="status"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>任务状态</FormLabel>
|
||||||
|
<Select value={field.value} onValueChange={field.onChange}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="ENABLED">启用</SelectItem>
|
||||||
|
<SelectItem value="DISABLED">禁用</SelectItem>
|
||||||
|
<SelectItem value="PAUSED">暂停</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 高级配置 */}
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="timeoutSeconds"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>超时时间(秒)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="300"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="retryCount"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>重试次数</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="formDefinitionId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>关联表单ID</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="可选"
|
||||||
|
value={field.value || ''}
|
||||||
|
onChange={(e) => field.onChange(e.target.value ? Number(e.target.value) : undefined)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="alertEmail"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>告警邮箱</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
placeholder="任务失败时发送告警的邮箱地址"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="concurrent"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex items-center justify-between rounded-lg border p-4">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel className="text-base">允许并发执行</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
是否允许任务并发执行(同时运行多个实例)
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
|
{/* 操作按钮 */}
|
||||||
|
<div className="flex items-center justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onOpenChange(false)}
|
||||||
|
disabled={submitting}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={submitting}>
|
||||||
|
{submitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
{job ? '保存' : '创建'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JobEditDialog;
|
||||||
|
|
||||||
@ -143,14 +143,14 @@ const JobLogDialog: React.FC<JobLogDialogProps> = ({
|
|||||||
{/* 筛选栏 */}
|
{/* 筛选栏 */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Select
|
<Select
|
||||||
value={query.status || ''}
|
value={query.status || 'all'}
|
||||||
onValueChange={(value) => setQuery({ ...query, status: value as LogStatus || undefined, pageNum: 0 })}
|
onValueChange={(value) => setQuery({ ...query, status: value !== 'all' ? value as LogStatus : undefined, pageNum: 0 })}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[150px]">
|
<SelectTrigger className="w-[150px]">
|
||||||
<SelectValue placeholder="全部状态" />
|
<SelectValue placeholder="全部状态" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">全部状态</SelectItem>
|
<SelectItem value="all">全部状态</SelectItem>
|
||||||
<SelectItem value="SUCCESS">成功</SelectItem>
|
<SelectItem value="SUCCESS">成功</SelectItem>
|
||||||
<SelectItem value="FAIL">失败</SelectItem>
|
<SelectItem value="FAIL">失败</SelectItem>
|
||||||
<SelectItem value="TIMEOUT">超时</SelectItem>
|
<SelectItem value="TIMEOUT">超时</SelectItem>
|
||||||
|
|||||||
@ -11,12 +11,23 @@ import {
|
|||||||
Clock, Activity, CheckCircle2, XCircle, FolderKanban, PlayCircle, FileText
|
Clock, Activity, CheckCircle2, XCircle, FolderKanban, PlayCircle, FileText
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { getScheduleJobs, getJobCategoryList, executeJobNow, pauseJob, resumeJob } from './service';
|
import { getScheduleJobs, getJobCategoryList, executeJobNow, pauseJob, resumeJob, deleteScheduleJob } from './service';
|
||||||
import type { ScheduleJobResponse, ScheduleJobQuery, JobCategoryResponse, JobStatus } from './types';
|
import type { ScheduleJobResponse, ScheduleJobQuery, JobCategoryResponse, JobStatus } from './types';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
||||||
import CategoryManageDialog from './components/CategoryManageDialog';
|
import CategoryManageDialog from './components/CategoryManageDialog';
|
||||||
import JobLogDialog from './components/JobLogDialog';
|
import JobLogDialog from './components/JobLogDialog';
|
||||||
|
import JobEditDialog from './components/JobEditDialog';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,7 +40,11 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
const [categories, setCategories] = useState<JobCategoryResponse[]>([]);
|
const [categories, setCategories] = useState<JobCategoryResponse[]>([]);
|
||||||
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
const [categoryDialogOpen, setCategoryDialogOpen] = useState(false);
|
||||||
const [logDialogOpen, setLogDialogOpen] = useState(false);
|
const [logDialogOpen, setLogDialogOpen] = useState(false);
|
||||||
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [selectedJob, setSelectedJob] = useState<ScheduleJobResponse | null>(null);
|
const [selectedJob, setSelectedJob] = useState<ScheduleJobResponse | null>(null);
|
||||||
|
const [editJob, setEditJob] = useState<ScheduleJobResponse | null>(null);
|
||||||
|
const [deleteJob, setDeleteJob] = useState<ScheduleJobResponse | null>(null);
|
||||||
const [query, setQuery] = useState<ScheduleJobQuery>({
|
const [query, setQuery] = useState<ScheduleJobQuery>({
|
||||||
pageNum: DEFAULT_CURRENT - 1,
|
pageNum: DEFAULT_CURRENT - 1,
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
@ -85,6 +100,45 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 新建任务
|
||||||
|
const handleCreate = () => {
|
||||||
|
setEditJob(null);
|
||||||
|
setEditDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑任务
|
||||||
|
const handleEdit = (record: ScheduleJobResponse) => {
|
||||||
|
setEditJob(record);
|
||||||
|
setEditDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开删除确认对话框
|
||||||
|
const handleDeleteClick = (record: ScheduleJobResponse) => {
|
||||||
|
setDeleteJob(record);
|
||||||
|
setDeleteDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认删除
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (!deleteJob) return;
|
||||||
|
try {
|
||||||
|
await deleteScheduleJob(deleteJob.id);
|
||||||
|
toast({
|
||||||
|
title: '删除成功',
|
||||||
|
description: `任务 "${deleteJob.jobName}" 已删除`,
|
||||||
|
});
|
||||||
|
loadData();
|
||||||
|
setDeleteDialogOpen(false);
|
||||||
|
setDeleteJob(null);
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
variant: 'destructive',
|
||||||
|
title: '删除失败',
|
||||||
|
description: error instanceof Error ? error.message : '未知错误',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 立即执行
|
// 立即执行
|
||||||
const handleExecute = async (record: ScheduleJobResponse) => {
|
const handleExecute = async (record: ScheduleJobResponse) => {
|
||||||
try {
|
try {
|
||||||
@ -228,7 +282,7 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
<FolderKanban className="h-4 w-4 mr-2" />
|
<FolderKanban className="h-4 w-4 mr-2" />
|
||||||
分类管理
|
分类管理
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => toast({ title: '功能开发中' })}>
|
<Button onClick={handleCreate}>
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
新建任务
|
新建任务
|
||||||
</Button>
|
</Button>
|
||||||
@ -246,14 +300,14 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Select
|
<Select
|
||||||
value={query.categoryId?.toString()}
|
value={query.categoryId?.toString() || 'all'}
|
||||||
onValueChange={(value) => setQuery({ ...query, categoryId: value ? Number(value) : undefined, pageNum: 0 })}
|
onValueChange={(value) => setQuery({ ...query, categoryId: value !== 'all' ? Number(value) : undefined, pageNum: 0 })}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[200px]">
|
<SelectTrigger className="w-[200px]">
|
||||||
<SelectValue placeholder="全部分类" />
|
<SelectValue placeholder="全部分类" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">全部分类</SelectItem>
|
<SelectItem value="all">全部分类</SelectItem>
|
||||||
{categories.map((cat) => (
|
{categories.map((cat) => (
|
||||||
<SelectItem key={cat.id} value={cat.id.toString()}>
|
<SelectItem key={cat.id} value={cat.id.toString()}>
|
||||||
{cat.name}
|
{cat.name}
|
||||||
@ -262,14 +316,14 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Select
|
<Select
|
||||||
value={query.status || ''}
|
value={query.status || 'all'}
|
||||||
onValueChange={(value) => setQuery({ ...query, status: value as JobStatus || undefined, pageNum: 0 })}
|
onValueChange={(value) => setQuery({ ...query, status: value !== 'all' ? value as JobStatus : undefined, pageNum: 0 })}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[150px]">
|
<SelectTrigger className="w-[150px]">
|
||||||
<SelectValue placeholder="全部状态" />
|
<SelectValue placeholder="全部状态" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">全部状态</SelectItem>
|
<SelectItem value="all">全部状态</SelectItem>
|
||||||
<SelectItem value="ENABLED">启用</SelectItem>
|
<SelectItem value="ENABLED">启用</SelectItem>
|
||||||
<SelectItem value="DISABLED">禁用</SelectItem>
|
<SelectItem value="DISABLED">禁用</SelectItem>
|
||||||
<SelectItem value="PAUSED">暂停</SelectItem>
|
<SelectItem value="PAUSED">暂停</SelectItem>
|
||||||
@ -394,7 +448,7 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-7 w-7"
|
className="h-7 w-7"
|
||||||
onClick={() => toast({ title: '功能开发中' })}
|
onClick={() => handleEdit(record)}
|
||||||
title="编辑"
|
title="编辑"
|
||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
@ -403,7 +457,7 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-7 w-7 text-destructive hover:text-destructive"
|
className="h-7 w-7 text-destructive hover:text-destructive"
|
||||||
onClick={() => toast({ title: '功能开发中' })}
|
onClick={() => handleDeleteClick(record)}
|
||||||
title="删除"
|
title="删除"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
@ -450,6 +504,66 @@ const ScheduleJobList: React.FC = () => {
|
|||||||
onOpenChange={setLogDialogOpen}
|
onOpenChange={setLogDialogOpen}
|
||||||
job={selectedJob}
|
job={selectedJob}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 任务编辑对话框 */}
|
||||||
|
<JobEditDialog
|
||||||
|
open={editDialogOpen}
|
||||||
|
onOpenChange={setEditDialogOpen}
|
||||||
|
job={editJob}
|
||||||
|
categories={categories}
|
||||||
|
onSuccess={loadData}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 删除确认对话框 */}
|
||||||
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确认删除定时任务</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription asChild>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p>您确定要删除以下定时任务吗?此操作无法撤销。</p>
|
||||||
|
{deleteJob && (
|
||||||
|
<div className="rounded-md border p-3 space-y-2 bg-muted/50">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-muted-foreground">任务名称:</span>
|
||||||
|
<span className="font-medium">{deleteJob.jobName}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-muted-foreground">Bean名称:</span>
|
||||||
|
<code className="text-xs bg-background px-2 py-1 rounded">
|
||||||
|
{deleteJob.beanName}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-muted-foreground">方法名称:</span>
|
||||||
|
<code className="text-xs bg-background px-2 py-1 rounded">
|
||||||
|
{deleteJob.methodName}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
{deleteJob.cronExpression && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-muted-foreground">Cron表达式:</span>
|
||||||
|
<code className="text-xs bg-background px-2 py-1 rounded">
|
||||||
|
{deleteJob.cronExpression}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={confirmDelete}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
>
|
||||||
|
确认删除
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
@ -231,7 +232,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
<DialogContent className="max-w-6xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<FolderKanban className="h-5 w-5" />
|
<FolderKanban className="h-5 w-5" />
|
||||||
@ -239,6 +240,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
{!editMode ? (
|
{!editMode ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* 搜索栏 */}
|
{/* 搜索栏 */}
|
||||||
@ -488,18 +490,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
|
||||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button type="submit">
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
|
{editMode && (
|
||||||
|
<div className="flex items-center justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||||
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={form.handleSubmit(handleSave)}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogBody, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
@ -195,7 +195,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={visible} onOpenChange={handleClose}>
|
<Dialog open={visible} onOpenChange={handleClose}>
|
||||||
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
|
<DialogContent className="max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@ -203,6 +203,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@ -311,6 +312,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => handleClose(false)} disabled={submitting}>
|
<Button variant="outline" onClick={() => handleClose(false)} disabled={submitting}>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
@ -107,10 +108,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (record) {
|
if (record) {
|
||||||
await updateMenu(record.id, {
|
await updateMenu(record.id, formData as MenuRequest);
|
||||||
...formData as MenuRequest,
|
|
||||||
version: record.version
|
|
||||||
});
|
|
||||||
toast({
|
toast({
|
||||||
title: '更新成功',
|
title: '更新成功',
|
||||||
description: `菜单 "${formData.name}" 已更新`,
|
description: `菜单 "${formData.name}" 已更新`,
|
||||||
@ -156,11 +154,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{record ? '编辑菜单' : '新增菜单'}</DialogTitle>
|
<DialogTitle>{record ? '编辑菜单' : '新增菜单'}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-4">
|
<DialogBody>
|
||||||
|
<div className="grid gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="type">菜单类型 *</Label>
|
<Label htmlFor="type">菜单类型 *</Label>
|
||||||
<Select
|
<Select
|
||||||
@ -285,6 +284,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DialogBody>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
取消
|
取消
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
@ -271,7 +272,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
<DialogContent className="max-w-6xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<FolderKanban className="h-5 w-5" />
|
<FolderKanban className="h-5 w-5" />
|
||||||
@ -279,6 +280,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
{!editMode ? (
|
{!editMode ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* 搜索和新建 */}
|
{/* 搜索和新建 */}
|
||||||
@ -567,18 +569,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center justify-end gap-2 pt-4 border-t">
|
|
||||||
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button type="submit">
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
|
{editMode && (
|
||||||
|
<div className="flex items-center justify-end gap-2 flex-shrink-0 px-6 pb-6 pt-4 border-t">
|
||||||
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button onClick={form.handleSubmit(handleSave)}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogBody, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table';
|
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -73,12 +73,13 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={visible} onOpenChange={(open) => !open && onCancel()}>
|
<Dialog open={visible} onOpenChange={(open) => !open && onCancel()}>
|
||||||
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
|
<DialogContent className="max-w-6xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>历史执行记录</DialogTitle>
|
<DialogTitle>历史执行记录</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="mt-4">
|
<DialogBody>
|
||||||
|
<div>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex justify-center items-center py-8">
|
<div className="flex justify-center items-center py-8">
|
||||||
<div className="text-muted-foreground">加载中...</div>
|
<div className="text-muted-foreground">加载中...</div>
|
||||||
@ -137,6 +138,7 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</DialogBody>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user