增加生成后端服务代码。

This commit is contained in:
dengqichen 2025-10-29 22:35:14 +08:00
parent ffbad526ac
commit d5c4bb0c1f

View File

@ -19,15 +19,18 @@ import { getDashboard, getScheduleJobs } from '../service';
import type { DashboardResponse, ScheduleJobResponse, LogStatus } from '../types'; import type { DashboardResponse, ScheduleJobResponse, LogStatus } from '../types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime'; import relativeTime from 'dayjs/plugin/relativeTime';
import duration from 'dayjs/plugin/duration';
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
dayjs.extend(duration);
dayjs.locale('zh-cn'); dayjs.locale('zh-cn');
const Dashboard: React.FC = () => { const Dashboard: React.FC = () => {
const [data, setData] = useState<DashboardResponse | null>(null); const [data, setData] = useState<DashboardResponse | null>(null);
const [allJobs, setAllJobs] = useState<ScheduleJobResponse[]>([]); const [allJobs, setAllJobs] = useState<ScheduleJobResponse[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [currentTime, setCurrentTime] = useState(dayjs());
// 加载仪表盘数据 // 加载仪表盘数据
const loadData = async () => { const loadData = async () => {
@ -52,6 +55,14 @@ const Dashboard: React.FC = () => {
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
// 实时更新当前时间(用于倒计时)
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(dayjs());
}, 1000);
return () => clearInterval(timer);
}, []);
// 获取日志状态徽章 // 获取日志状态徽章
const getLogStatusBadge = (status: LogStatus) => { const getLogStatusBadge = (status: LogStatus) => {
const statusMap: Record<LogStatus, { const statusMap: Record<LogStatus, {
@ -94,6 +105,22 @@ const Dashboard: React.FC = () => {
.slice(0, 10); .slice(0, 10);
}, [allJobs]); }, [allJobs]);
// 格式化倒计时
const formatCountdown = (nextTime: string) => {
const diff = dayjs(nextTime).diff(currentTime);
if (diff <= 0) return '即将执行';
const dur = dayjs.duration(diff);
const days = dur.days();
const hours = dur.hours();
const minutes = dur.minutes();
const seconds = dur.seconds();
if (days > 0) return `${days}${hours}小时后`;
if (hours > 0) return `${hours}小时${minutes}分后`;
if (minutes > 0) return `${minutes}${seconds}秒后`;
return `${seconds}秒后`;
};
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-96"> <div className="flex items-center justify-center h-96">
@ -103,9 +130,9 @@ const Dashboard: React.FC = () => {
} }
return ( return (
<div className="space-y-6"> <div className="flex flex-col h-full space-y-4">
{/* 任务概览统计卡片 */} {/* 任务概览统计卡片 */}
<div className="grid gap-4 md:grid-cols-5"> <div className="grid gap-4 md:grid-cols-5 flex-shrink-0">
<Card className="border-l-4 border-l-blue-500"> <Card className="border-l-4 border-l-blue-500">
<CardHeader className="flex flex-row items-center justify-between pb-2"> <CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium"></CardTitle> <CardTitle className="text-sm font-medium"></CardTitle>
@ -166,45 +193,9 @@ const Dashboard: React.FC = () => {
</Card> </Card>
</div> </div>
{/* 正在执行的任务 */}
{data && data.runningJobs.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Loader2 className="h-5 w-5 animate-spin text-orange-500" />
({data.runningJobs.length})
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{data.runningJobs.map((job) => (
<div key={job.jobId} className="p-4 border rounded-lg space-y-3">
<div className="flex items-start justify-between">
<div>
<h4 className="font-semibold text-lg">{job.jobName}</h4>
<p className="text-sm text-muted-foreground mt-1">
: {dayjs(job.startTime).format('YYYY-MM-DD HH:mm:ss')}
</p>
</div>
<Badge variant="outline" className="text-orange-600 border-orange-600">
{job.progress}%
</Badge>
</div>
<Progress value={job.progress} className="h-3" />
{job.message && (
<p className="text-sm text-muted-foreground flex items-center gap-2">
<Activity className="h-4 w-4" />
{job.message}
</p>
)}
</div>
))}
</CardContent>
</Card>
)}
{/* 多视图 Tabs */} {/* 多视图 Tabs */}
<Tabs defaultValue="timeline" className="space-y-4"> <Tabs defaultValue="timeline" className="flex-1 flex flex-col min-h-0">
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-3 flex-shrink-0">
<TabsTrigger value="timeline"> <TabsTrigger value="timeline">
<Calendar className="h-4 w-4 mr-2" /> <Calendar className="h-4 w-4 mr-2" />
线 线
@ -220,22 +211,23 @@ const Dashboard: React.FC = () => {
</TabsList> </TabsList>
{/* 时间线视图 */} {/* 时间线视图 */}
<TabsContent value="timeline"> <TabsContent value="timeline" className="flex-1 min-h-0 mt-4">
<Card> <Card className="h-full flex flex-col">
<CardHeader> <CardHeader className="flex-shrink-0">
<CardTitle>线</CardTitle> <CardTitle>线</CardTitle>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
</p> </p>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="flex-1 min-h-0 overflow-hidden">
<ScrollArea className="h-full">
{upcomingJobs.length === 0 ? ( {upcomingJobs.length === 0 ? (
<div className="text-center py-8 text-muted-foreground"> <div className="text-center py-8 text-muted-foreground">
<Clock className="h-12 w-12 mx-auto mb-2 opacity-20" /> <Clock className="h-12 w-12 mx-auto mb-2 opacity-20" />
<p></p> <p></p>
</div> </div>
) : ( ) : (
<div className="relative pl-8"> <div className="relative pl-8 pr-4">
{/* 时间线 */} {/* 时间线 */}
<div className="absolute left-3 top-0 bottom-0 w-0.5 bg-border" /> <div className="absolute left-3 top-0 bottom-0 w-0.5 bg-border" />
@ -276,13 +268,14 @@ const Dashboard: React.FC = () => {
))} ))}
</div> </div>
)} )}
</ScrollArea>
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
{/* 状态分组视图 */} {/* 状态分组视图 */}
<TabsContent value="status"> <TabsContent value="status" className="flex-1 min-h-0 mt-4">
<div className="grid gap-4 md:grid-cols-4 h-[calc(100vh-280px)]"> <div className="grid gap-4 md:grid-cols-4 h-full">
{/* 运行中 */} {/* 运行中 */}
<Card className="border-l-4 border-l-orange-500 flex flex-col"> <Card className="border-l-4 border-l-orange-500 flex flex-col">
<CardHeader className="pb-3 flex-shrink-0"> <CardHeader className="pb-3 flex-shrink-0">
@ -387,7 +380,7 @@ const Dashboard: React.FC = () => {
</p> </p>
<p className="text-xs text-muted-foreground flex items-center gap-1"> <p className="text-xs text-muted-foreground flex items-center gap-1">
<Clock className="h-3 w-3" /> <Clock className="h-3 w-3" />
: {job.nextExecuteTime ? dayjs(job.nextExecuteTime).format('MM-DD HH:mm') : '-'} {job.nextExecuteTime ? formatCountdown(job.nextExecuteTime) : '-'}
</p> </p>
</div> </div>
))} ))}
@ -432,19 +425,20 @@ const Dashboard: React.FC = () => {
</TabsContent> </TabsContent>
{/* 最近日志 */} {/* 最近日志 */}
<TabsContent value="logs"> <TabsContent value="logs" className="flex-1 min-h-0 mt-4">
<Card> <Card className="h-full flex flex-col">
<CardHeader> <CardHeader className="flex-shrink-0">
<CardTitle> (10)</CardTitle> <CardTitle> (10)</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="flex-1 min-h-0 overflow-hidden">
<ScrollArea className="h-full">
{!data || data.recentLogs.length === 0 ? ( {!data || data.recentLogs.length === 0 ? (
<div className="text-center py-8 text-muted-foreground"> <div className="text-center py-8 text-muted-foreground">
<AlertCircle className="h-12 w-12 mx-auto mb-2 opacity-20" /> <AlertCircle className="h-12 w-12 mx-auto mb-2 opacity-20" />
<p></p> <p></p>
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3 pr-4">
{data.recentLogs.map((log) => ( {data.recentLogs.map((log) => (
<div key={log.id} className="p-3 border rounded-lg hover:bg-accent transition-colors"> <div key={log.id} className="p-3 border rounded-lg hover:bg-accent transition-colors">
<div className="flex items-start justify-between mb-2"> <div className="flex items-start justify-between mb-2">
@ -470,6 +464,7 @@ const Dashboard: React.FC = () => {
))} ))}
</div> </div>
)} )}
</ScrollArea>
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>