/** * 定时任务管理页面。 * * 展示 biz.trigger_jobs 表中所有定时任务,支持手动执行。 */ import React, { useEffect, useState, useCallback } from 'react'; import { Table, Tag, Button, message, Modal, Typography, Card, Space, Popconfirm, Tooltip } from 'antd'; import { ReloadOutlined, ClockCircleOutlined, PlayCircleOutlined, CheckCircleOutlined, ExclamationCircleOutlined, } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import { fetchTriggerJobs, runTriggerJob, clearAllTasks, type TriggerJob } from '../api/triggerJobs'; const { Title, Text } = Typography; const TRIGGER_LABEL: Record = { cron: '定时(Cron)', interval: '间隔', event: '事件触发', }; const STATUS_COLOR: Record = { enabled: 'green', disabled: 'default', }; function formatTime(raw: string | null): string { if (!raw) return '—'; const d = new Date(raw); return Number.isNaN(d.getTime()) ? raw : d.toLocaleString('zh-CN'); } function formatTriggerConfig(job: TriggerJob): string { const cfg = job.trigger_config; if (!cfg) return '—'; if (job.trigger_condition === 'cron') return cfg.cron_expression as string || '—'; if (job.trigger_condition === 'interval') { const sec = cfg.interval_seconds as number; if (sec >= 3600) return `每 ${sec / 3600} 小时`; if (sec >= 60) return `每 ${sec / 60} 分钟`; return `每 ${sec} 秒`; } if (job.trigger_condition === 'event') return `事件: ${cfg.event_name || '—'}`; return JSON.stringify(cfg); } const TriggerJobs: React.FC = () => { const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(false); const [runningId, setRunningId] = useState(null); const [clearing, setClearing] = useState(false); const load = useCallback(async () => { setLoading(true); try { const data = await fetchTriggerJobs(); setJobs(data); } catch { message.error('加载定时任务失败'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const handleRun = async (jobId: number) => { setRunningId(jobId); try { const result = await runTriggerJob(jobId); if (result.success) { message.success(result.message); } else { message.error(result.message); } await load(); } catch { message.error('执行失败'); } finally { setRunningId(null); } }; const handleClearAllTasks = async () => { setClearing(true); try { const result = await clearAllTasks(); if (result.success) { Modal.success({ title: '清空完成', content: result.message, }); await load(); } else { message.error(result.message); } } catch { message.error('清空任务失败'); } finally { setClearing(false); } }; const columns: ColumnsType = [ { title: '任务名称', dataIndex: 'job_name', key: 'job_name', width: 180, render: (name: string, record) => ( {record.description || name}
{name}
), }, { title: '触发方式', dataIndex: 'trigger_condition', key: 'trigger_condition', width: 120, render: (v: string) => {TRIGGER_LABEL[v] || v}, }, { title: '触发配置', key: 'trigger_config', width: 150, render: (_: unknown, record) => {formatTriggerConfig(record)}, }, { title: '状态', dataIndex: 'status', key: 'status', width: 80, render: (v: string) => {v === 'enabled' ? '启用' : '禁用'}, }, { title: '上次执行', dataIndex: 'last_run_at', key: 'last_run_at', width: 170, render: (v: string | null) => formatTime(v), }, { title: '下次执行', dataIndex: 'next_run_at', key: 'next_run_at', width: 170, render: (v: string | null) => formatTime(v), }, { title: '最近错误', dataIndex: 'last_error', key: 'last_error', width: 200, render: (v: string | null) => v ? {v} : 正常, }, { title: '操作', key: 'action', width: 100, fixed: 'right', render: (_: unknown, record) => ( handleRun(record.id)} okText="执行" cancelText="取消" > ), }, ]; return (
<ClockCircleOutlined style={{ marginRight: 8 }} /> 定时任务管理
rowKey="id" columns={columns} dataSource={jobs} loading={loading} pagination={false} size="small" scroll={{ x: 1200 }} />
); }; export default TriggerJobs;