增加团队管理页面

This commit is contained in:
dengqichen 2025-10-29 17:16:48 +08:00
parent bd984da237
commit e4cabb125a
15 changed files with 666 additions and 74 deletions

View File

@ -64,7 +64,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
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
)}
{...props}
@ -85,7 +85,7 @@ const DialogHeader = ({
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
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
)}
{...props}
@ -93,13 +93,27 @@ const 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 = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
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
)}
{...props}
@ -142,6 +156,7 @@ export {
DialogTrigger,
DialogContent,
DialogHeader,
DialogBody,
DialogFooter,
DialogTitle,
DialogDescription,

View File

@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
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
)}
{...props}

View File

@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogHeader,
DialogBody,
DialogTitle,
} from "@/components/ui/dialog";
import {
@ -234,7 +235,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
<DialogContent className="max-w-6xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FolderKanban className="h-5 w-5" />
@ -242,6 +243,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
</DialogTitle>
</DialogHeader>
<DialogBody>
{!editMode ? (
<div className="space-y-4">
{/* 搜索栏 */}
@ -495,18 +497,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
</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>
)}
</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>
</Dialog>

View File

@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogHeader,
DialogBody,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
@ -92,11 +93,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>{record ? '编辑环境' : '新建环境'}</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<DialogBody>
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="tenantCode"> *</Label>
<Input
@ -160,6 +162,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
/>
</div>
</div>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>

View File

@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogHeader,
DialogBody,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
@ -123,11 +124,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>{record ? '编辑系统' : '新增系统'}</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<DialogBody>
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="name"> *</Label>
<Input
@ -250,6 +252,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
/>
</div>
</div>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>

View File

@ -28,6 +28,8 @@ export interface RepositoryGroupResponse extends BaseResponse {
repoGroupId?: number;
/** 外部系统ID */
externalSystemId: number;
/** 项目数量 */
projectCount?: number;
/** 子仓库组列表(用于树形结构) */
children?: RepositoryGroupResponse[];
}
@ -60,6 +62,8 @@ export interface RepositoryProjectResponse extends BaseResponse {
repoGroupId?: number;
/** 外部系统中的项目ID */
repoProjectId?: number;
/** 分支数量 */
branchCount?: number;
}
/**

View File

@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogHeader,
DialogBody,
DialogTitle,
} from '@/components/ui/dialog';
import {
@ -239,7 +240,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
<DialogContent className="max-w-6xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FolderKanban className="h-5 w-5" />
@ -247,6 +248,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
</DialogTitle>
</DialogHeader>
<DialogBody>
{!editMode ? (
<div className="space-y-4">
{/* 搜索和新建 */}
@ -370,8 +372,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
<DataTablePagination
pageIndex={pageNum}
pageSize={pageSize}
totalPages={data.totalPages}
totalElements={data.totalElements}
pageCount={data.totalPages}
onPageChange={setPageNum}
/>
)}
@ -514,18 +515,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
</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>
)}
</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>
</Dialog>

View File

@ -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;

View File

@ -143,14 +143,14 @@ const JobLogDialog: React.FC<JobLogDialogProps> = ({
{/* 筛选栏 */}
<div className="flex items-center gap-4">
<Select
value={query.status || ''}
onValueChange={(value) => setQuery({ ...query, status: value as LogStatus || undefined, pageNum: 0 })}
value={query.status || 'all'}
onValueChange={(value) => setQuery({ ...query, status: value !== 'all' ? value as LogStatus : undefined, pageNum: 0 })}
>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="全部状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value=""></SelectItem>
<SelectItem value="all"></SelectItem>
<SelectItem value="SUCCESS"></SelectItem>
<SelectItem value="FAIL"></SelectItem>
<SelectItem value="TIMEOUT"></SelectItem>

View File

@ -11,12 +11,23 @@ import {
Clock, Activity, CheckCircle2, XCircle, FolderKanban, PlayCircle, FileText
} from 'lucide-react';
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 { Page } from '@/types/base';
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
import CategoryManageDialog from './components/CategoryManageDialog';
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';
/**
@ -29,7 +40,11 @@ const ScheduleJobList: React.FC = () => {
const [categories, setCategories] = useState<JobCategoryResponse[]>([]);
const [categoryDialogOpen, setCategoryDialogOpen] = 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 [editJob, setEditJob] = useState<ScheduleJobResponse | null>(null);
const [deleteJob, setDeleteJob] = useState<ScheduleJobResponse | null>(null);
const [query, setQuery] = useState<ScheduleJobQuery>({
pageNum: DEFAULT_CURRENT - 1,
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) => {
try {
@ -228,7 +282,7 @@ const ScheduleJobList: React.FC = () => {
<FolderKanban className="h-4 w-4 mr-2" />
</Button>
<Button onClick={() => toast({ title: '功能开发中' })}>
<Button onClick={handleCreate}>
<Plus className="h-4 w-4 mr-2" />
</Button>
@ -246,14 +300,14 @@ const ScheduleJobList: React.FC = () => {
/>
</div>
<Select
value={query.categoryId?.toString()}
onValueChange={(value) => setQuery({ ...query, categoryId: value ? Number(value) : undefined, pageNum: 0 })}
value={query.categoryId?.toString() || 'all'}
onValueChange={(value) => setQuery({ ...query, categoryId: value !== 'all' ? Number(value) : undefined, pageNum: 0 })}
>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="全部分类" />
</SelectTrigger>
<SelectContent>
<SelectItem value=""></SelectItem>
<SelectItem value="all"></SelectItem>
{categories.map((cat) => (
<SelectItem key={cat.id} value={cat.id.toString()}>
{cat.name}
@ -262,14 +316,14 @@ const ScheduleJobList: React.FC = () => {
</SelectContent>
</Select>
<Select
value={query.status || ''}
onValueChange={(value) => setQuery({ ...query, status: value as JobStatus || undefined, pageNum: 0 })}
value={query.status || 'all'}
onValueChange={(value) => setQuery({ ...query, status: value !== 'all' ? value as JobStatus : undefined, pageNum: 0 })}
>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="全部状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value=""></SelectItem>
<SelectItem value="all"></SelectItem>
<SelectItem value="ENABLED"></SelectItem>
<SelectItem value="DISABLED"></SelectItem>
<SelectItem value="PAUSED"></SelectItem>
@ -394,7 +448,7 @@ const ScheduleJobList: React.FC = () => {
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={() => toast({ title: '功能开发中' })}
onClick={() => handleEdit(record)}
title="编辑"
>
<Edit className="h-4 w-4" />
@ -403,7 +457,7 @@ const ScheduleJobList: React.FC = () => {
variant="ghost"
size="icon"
className="h-7 w-7 text-destructive hover:text-destructive"
onClick={() => toast({ title: '功能开发中' })}
onClick={() => handleDeleteClick(record)}
title="删除"
>
<Trash2 className="h-4 w-4" />
@ -450,6 +504,66 @@ const ScheduleJobList: React.FC = () => {
onOpenChange={setLogDialogOpen}
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>
);
};

View File

@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogHeader,
DialogBody,
DialogTitle,
} from "@/components/ui/dialog";
import {
@ -231,7 +232,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
<DialogContent className="max-w-6xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FolderKanban className="h-5 w-5" />
@ -239,6 +240,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
</DialogTitle>
</DialogHeader>
<DialogBody>
{!editMode ? (
<div className="space-y-4">
{/* 搜索栏 */}
@ -488,18 +490,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
</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>
)}
</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>
</Dialog>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
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 { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@ -195,7 +195,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
return (
<Dialog open={visible} onOpenChange={handleClose}>
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>
@ -203,6 +203,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
</DialogDescription>
</DialogHeader>
<DialogBody>
<Separator />
<div className="space-y-6">
@ -311,6 +312,7 @@ const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
</p>
</div>
</div>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => handleClose(false)} disabled={submitting}>

View File

@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogHeader,
DialogBody,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
@ -107,10 +108,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
}
if (record) {
await updateMenu(record.id, {
...formData as MenuRequest,
version: record.version
});
await updateMenu(record.id, formData as MenuRequest);
toast({
title: '更新成功',
description: `菜单 "${formData.name}" 已更新`,
@ -156,11 +154,12 @@ const EditDialog: React.FC<EditDialogProps> = ({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>{record ? '编辑菜单' : '新增菜单'}</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<DialogBody>
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="type"> *</Label>
<Select
@ -285,6 +284,7 @@ const EditDialog: React.FC<EditDialogProps> = ({
/>
</div>
</div>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>

View File

@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogHeader,
DialogBody,
DialogTitle,
} from "@/components/ui/dialog";
import {
@ -271,7 +272,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
<DialogContent className="max-w-6xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FolderKanban className="h-5 w-5" />
@ -279,6 +280,7 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
</DialogTitle>
</DialogHeader>
<DialogBody>
{!editMode ? (
<div className="space-y-4">
{/* 搜索和新建 */}
@ -567,18 +569,21 @@ const CategoryManageDialog: React.FC<CategoryManageDialogProps> = ({
</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>
)}
</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>
</Dialog>

View File

@ -1,5 +1,5 @@
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 { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -73,12 +73,13 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
return (
<>
<Dialog open={visible} onOpenChange={(open) => !open && onCancel()}>
<DialogContent className="max-w-6xl max-h-[85vh] overflow-y-auto">
<DialogContent className="max-w-6xl">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="mt-4">
<DialogBody>
<div>
{loading ? (
<div className="flex justify-center items-center py-8">
<div className="text-muted-foreground">...</div>
@ -137,6 +138,7 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
</>
)}
</div>
</DialogBody>
</DialogContent>
</Dialog>