增加审批组件

This commit is contained in:
dengqichen 2025-10-29 22:02:37 +08:00
parent 7c49715172
commit ffbad526ac
4 changed files with 924 additions and 672 deletions

View File

@ -118,12 +118,15 @@ const DataTablePagination: React.FC<DataTablePaginationProps> = ({
pageCount,
onPageChange,
}) => {
// 将 0-based 的 pageIndex 转换为 1-based 的显示页码
const currentPage = pageIndex + 1;
const renderPageNumbers = () => {
const pages = [];
const maxVisiblePages = 5;
const halfMaxVisiblePages = Math.floor(maxVisiblePages / 2);
let startPage = Math.max(1, pageIndex - halfMaxVisiblePages);
let startPage = Math.max(1, currentPage - halfMaxVisiblePages);
let endPage = Math.min(pageCount, startPage + maxVisiblePages - 1);
if (endPage - startPage + 1 < maxVisiblePages) {
@ -134,7 +137,12 @@ const DataTablePagination: React.FC<DataTablePaginationProps> = ({
if (startPage > 1) {
pages.push(
<PaginationItem key={1}>
<PaginationLink onClick={() => onPageChange(1)}>1</PaginationLink>
<PaginationLink
isActive={currentPage === 1}
onClick={() => onPageChange(0)}
>
1
</PaginationLink>
</PaginationItem>
);
if (startPage > 2) {
@ -151,8 +159,8 @@ const DataTablePagination: React.FC<DataTablePaginationProps> = ({
pages.push(
<PaginationItem key={i}>
<PaginationLink
isActive={i === pageIndex}
onClick={() => onPageChange(i)}
isActive={i === currentPage}
onClick={() => onPageChange(i - 1)}
>
{i}
</PaginationLink>
@ -171,7 +179,10 @@ const DataTablePagination: React.FC<DataTablePaginationProps> = ({
}
pages.push(
<PaginationItem key={pageCount}>
<PaginationLink onClick={() => onPageChange(pageCount)}>
<PaginationLink
isActive={currentPage === pageCount}
onClick={() => onPageChange(pageCount - 1)}
>
{pageCount}
</PaginationLink>
</PaginationItem>
@ -186,15 +197,15 @@ const DataTablePagination: React.FC<DataTablePaginationProps> = ({
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={() => onPageChange(Math.max(1, pageIndex - 1))}
className={cn(pageIndex <= 1 && "pointer-events-none opacity-50")}
onClick={() => onPageChange(Math.max(0, pageIndex - 1))}
className={cn(pageIndex <= 0 && "pointer-events-none opacity-50")}
/>
</PaginationItem>
{renderPageNumbers()}
<PaginationItem>
<PaginationNext
onClick={() => onPageChange(Math.min(pageCount, pageIndex + 1))}
className={cn(pageIndex >= pageCount && "pointer-events-none opacity-50")}
onClick={() => onPageChange(Math.min(pageCount - 1, pageIndex + 1))}
className={cn(pageIndex >= pageCount - 1 && "pointer-events-none opacity-50")}
/>
</PaginationItem>
</PaginationContent>

View File

@ -344,9 +344,10 @@ const JenkinsManager: React.FC = () => {
};
return (
<div className="p-6 flex flex-col h-screen">
<TooltipProvider>
<div className="flex flex-col h-full p-6 gap-4">
{/* 页面标题 */}
<div className="flex items-center justify-between mb-6 flex-shrink-0">
<div className="flex items-center justify-between flex-shrink-0">
<div>
<h1 className="text-3xl font-bold text-foreground">Jenkins </h1>
<p className="text-muted-foreground mt-2">
@ -403,7 +404,7 @@ const JenkinsManager: React.FC = () => {
</div>
</CardHeader>
<ScrollArea className="flex-1">
<div className="p-2 space-y-1">
<div className="p-2 space-y-1 min-h-[calc(100vh-280px)]">
{loading.views ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
@ -434,7 +435,6 @@ const JenkinsManager: React.FC = () => {
</Badge>
)}
{view.viewUrl && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<a
@ -453,7 +453,6 @@ const JenkinsManager: React.FC = () => {
<p> Jenkins </p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
{view.description && (
@ -532,7 +531,6 @@ const JenkinsManager: React.FC = () => {
</Badge>
)}
{job.jobUrl && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<a
@ -551,7 +549,6 @@ const JenkinsManager: React.FC = () => {
<p> Jenkins </p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
@ -564,7 +561,6 @@ const JenkinsManager: React.FC = () => {
<div className="flex items-center gap-3 text-xs">
{job.lastBuildStatus && getBuildStatusBadge(job.lastBuildStatus)}
{job.healthReportScore !== undefined && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1">
@ -582,7 +578,6 @@ const JenkinsManager: React.FC = () => {
: {job.healthReportScore}%
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
@ -658,7 +653,6 @@ const JenkinsManager: React.FC = () => {
{getBuildStatusBadge(build.buildStatus)}
</div>
{build.buildUrl && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<a
@ -676,7 +670,6 @@ const JenkinsManager: React.FC = () => {
<p> Jenkins </p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
@ -701,6 +694,7 @@ const JenkinsManager: React.FC = () => {
</Card>
</div>
</div>
</TooltipProvider>
);
};

View File

@ -23,7 +23,7 @@ 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 { createScheduleJob, updateScheduleJob, updateJobCron } from '../service';
import type { ScheduleJobResponse, ScheduleJobRequest, JobCategoryResponse } from '../types';
interface JobEditDialogProps {
@ -46,6 +46,8 @@ const JobEditDialog: React.FC<JobEditDialogProps> = ({
}) => {
const { toast } = useToast();
const [submitting, setSubmitting] = React.useState(false);
// 保存原始的 Cron 表达式,用于判断是否需要更新调度器
const [originalCronExpression, setOriginalCronExpression] = React.useState<string>('');
const form = useForm<ScheduleJobRequest>({
defaultValues: {
@ -69,7 +71,8 @@ const JobEditDialog: React.FC<JobEditDialogProps> = ({
useEffect(() => {
if (open) {
if (job) {
// 编辑模式
// 编辑模式 - 保存原始的 Cron 表达式
setOriginalCronExpression(job.cronExpression);
form.reset({
jobName: job.jobName,
jobDescription: job.jobDescription || '',
@ -86,7 +89,8 @@ const JobEditDialog: React.FC<JobEditDialogProps> = ({
alertEmail: job.alertEmail || '',
});
} else {
// 新建模式
// 新建模式 - 重置原始 Cron 表达式
setOriginalCronExpression('');
form.reset({
jobName: '',
jobDescription: '',
@ -110,11 +114,35 @@ const JobEditDialog: React.FC<JobEditDialogProps> = ({
setSubmitting(true);
try {
if (job) {
// 更新任务
await updateScheduleJob(job.id, values);
// 检查 Cron 表达式是否变化
const cronChanged = values.cronExpression !== originalCronExpression;
if (cronChanged) {
// Cron 表达式变化了,需要额外调用更新 Cron 的接口
try {
await updateJobCron(job.id, values.cronExpression);
toast({
title: '更新成功',
description: `任务 "${values.jobName}" 已更新,调度器已重新配置`,
});
} catch (cronError) {
// Cron 更新失败,提示用户
toast({
variant: 'destructive',
title: '调度器更新失败',
description: '任务已保存,但调度器配置更新失败,请重试或联系管理员',
});
console.error('更新 Cron 失败:', cronError);
}
} else {
toast({
title: '更新成功',
description: `任务 "${values.jobName}" 已更新`,
});
}
} else {
await createScheduleJob(values);
toast({

File diff suppressed because it is too large Load Diff