增加生成后端服务代码。
This commit is contained in:
parent
ffbad526ac
commit
d5c4bb0c1f
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user