import React, { useState, useEffect, useCallback } from 'react'; import { Table, Button, Space, Typography, message, Card, Modal, Form, Input, Select, Switch, Tag, Popconfirm, Tabs, } from 'antd'; import { ClockCircleOutlined, PlusOutlined, EditOutlined, DeleteOutlined, PlayCircleOutlined, StopOutlined, CheckCircleOutlined, CloseCircleOutlined, } from '@ant-design/icons'; import { listScheduledTasks, createScheduledTask, updateScheduledTask, deleteScheduledTask, runScheduledTask, toggleScheduledTask, } from '../api'; import type { ScheduledTask } from '../types'; const { TextArea } = Input; const SCHEDULE_TYPE_OPTIONS = [ { value: 'external_api', label: '外部接口调用' }, { value: 'internal_method', label: '内部方法执行' }, ]; const TASK_TYPE_LABELS: Record = { external_api: '外部接口', internal_method: '内部方法', }; const STATUS_LABELS: Record = { success: { label: '成功', color: 'green' }, error: { label: '失败', color: 'red' }, }; const AdminScheduledTasks: React.FC = () => { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [modal, setModal] = useState(false); const [editing, setEditing] = useState(null); const [form] = Form.useForm(); const [saving, setSaving] = useState(false); const [activeTab, setActiveTab] = useState<'basic' | 'config'>('basic'); const fetchData = useCallback(async () => { setLoading(true); try { const res = await listScheduledTasks(); setData(res.items || []); } catch (err: any) { message.error(err?.message || '加载失败'); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); const openModal = (task?: ScheduledTask) => { if (task) { setEditing(task); let configStr = ''; if (task.config) { try { configStr = typeof task.config === 'string' ? JSON.stringify(JSON.parse(task.config), null, 2) : JSON.stringify(task.config, null, 2); } catch { configStr = task.config; } } form.setFieldsValue({ name: task.name, task_type: task.task_type, schedule: task.schedule, config: configStr, is_active: task.is_active, }); } else { setEditing(null); form.resetFields(); form.setFieldsValue({ is_active: true, task_type: 'external_api' }); } setActiveTab('basic'); setModal(true); }; const handleSave = async () => { try { const values = await form.validateFields(); let configVal = values.config; if (configVal && typeof configVal === 'string') { try { configVal = JSON.stringify(JSON.parse(configVal)); } catch { message.warning('配置 JSON 格式不合法,将按原样保存'); } } setSaving(true); if (editing) { await updateScheduledTask(editing.id, { ...values, config: configVal }); message.success('更新成功'); } else { await createScheduledTask({ ...values, config: configVal }); message.success('创建成功'); } setModal(false); await fetchData(); } catch (err: any) { message.error(err?.message || '保存失败'); } finally { setSaving(false); } }; const handleDelete = async (taskId: string) => { try { await deleteScheduledTask(taskId); message.success('删除成功'); await fetchData(); } catch (err: any) { message.error(err?.message || '删除失败'); } }; const handleRun = async (taskId: string) => { try { await runScheduledTask(taskId); message.success('任务已提交执行'); setTimeout(fetchData, 1500); } catch (err: any) { message.error(err?.message || '执行失败'); } }; const handleToggle = async (task: ScheduledTask) => { try { const res = await toggleScheduledTask(task.id); message.success(res.message); await fetchData(); } catch (err: any) { message.error(err?.message || '操作失败'); } }; const taskType = Form.useWatch('task_type', form); const columns = [ { title: '任务名称', dataIndex: 'name', width: 160, ellipsis: true }, { title: '类型', dataIndex: 'task_type', width: 100, render: (v: string) => {TASK_TYPE_LABELS[v] || v}, }, { title: '调度表达式', dataIndex: 'schedule', width: 140, render: (v: string) => {v} }, { title: '状态', dataIndex: 'is_active', width: 80, render: (v: boolean) => {v ? '启用' : '禁用'}, }, { title: '最后执行', dataIndex: 'last_run_at', width: 160, render: (v: string, r: ScheduledTask) => { if (!v) return '-'; const status = r.last_status ? STATUS_LABELS[r.last_status] : null; return ( {v.includes('T') ? v.replace('T', ' ').slice(0, 19) : v} {status && {status.label}} ); }, }, { title: '操作', width: 220, render: (_: any, record: ScheduledTask) => ( handleDelete(record.id)} okText="确定" cancelText="取消"> ), }, ]; const scheduleHelp = (
• 纯数字:间隔秒数(如 60 = 每 60 秒执行一次)
• Cron 表达式(5 字段):分 时 日 月 周
• 示例:* * * * * = 每分钟 | 0 * * * * = 每小时
); return (
定时任务管理 自定义定时执行外部接口调用或内部方法
`共 ${t} 条` }} /> {/* 新增/编辑弹窗 */} setModal(false)} confirmLoading={saving} okText="保存" cancelText="取消" destroyOnClose width={600} >
setActiveTab(k as 'basic' | 'config')} items={[ { key: 'basic', label: '基本设置', children: ( <> ), }, { key: 'config', label: '任务配置', children: (