// ============================================================ // 定时任务 — 管理页面 // ============================================================ import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Button, message, Tag, Modal, Form, Input, Select, Switch, Popconfirm, Space } from 'antd' import type { ProColumns } from '@ant-design/pro-components' import { ProTable } from '@ant-design/pro-components' import { PlusOutlined } from '@ant-design/icons' import { scheduledTaskService } from '@/services/scheduled-tasks' import type { ScheduledTask, CreateScheduledTaskRequest, UpdateScheduledTaskRequest } from '@/services/scheduled-tasks' import { PageHeader } from '@/components/PageHeader' import { ErrorState } from '@/components/ErrorState' const scheduleTypeLabels: Record = { cron: 'Cron', interval: '间隔', once: '一次性', } const scheduleTypeColors: Record = { cron: 'blue', interval: 'green', once: 'orange', } const targetTypeLabels: Record = { agent: 'Agent', hand: 'Hand', workflow: 'Workflow', } const targetTypeColors: Record = { agent: 'purple', hand: 'cyan', workflow: 'geekblue', } function formatDateTime(value: string | null): string { if (!value) return '-' return new Date(value).toLocaleString('zh-CN') } function formatDuration(ms: number | null): string { if (ms === null) return '-' if (ms < 1000) return `${ms}ms` return `${(ms / 1000).toFixed(2)}s` } export default function ScheduledTasks() { const queryClient = useQueryClient() const [form] = Form.useForm() const [modalOpen, setModalOpen] = useState(false) const [editingId, setEditingId] = useState(null) const { data, isLoading, error, refetch } = useQuery({ queryKey: ['scheduled-tasks'], queryFn: ({ signal }) => scheduledTaskService.list(signal), }) const createMutation = useMutation({ mutationFn: (data: CreateScheduledTaskRequest) => scheduledTaskService.create(data), onSuccess: () => { message.success('任务创建成功') queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] }) closeModal() }, onError: (err: Error) => message.error(err.message || '创建失败'), }) const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateScheduledTaskRequest }) => scheduledTaskService.update(id, data), onSuccess: () => { message.success('任务更新成功') queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] }) closeModal() }, onError: (err: Error) => message.error(err.message || '更新失败'), }) const deleteMutation = useMutation({ mutationFn: (id: string) => scheduledTaskService.delete(id), onSuccess: () => { message.success('任务已删除') queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] }) }, onError: (err: Error) => message.error(err.message || '删除失败'), }) const toggleMutation = useMutation({ mutationFn: ({ id, enabled }: { id: string; enabled: boolean }) => scheduledTaskService.update(id, { enabled }), onSuccess: () => { message.success('状态已更新') queryClient.invalidateQueries({ queryKey: ['scheduled-tasks'] }) }, onError: (err: Error) => message.error(err.message || '状态更新失败'), }) const columns: ProColumns[] = [ { title: '任务名称', dataIndex: 'name', width: 160, ellipsis: true, }, { title: '调度规则', dataIndex: 'schedule', width: 140, ellipsis: true, hideInSearch: true, }, { title: '调度类型', dataIndex: 'schedule_type', width: 100, valueType: 'select', valueEnum: { cron: { text: 'Cron' }, interval: { text: '间隔' }, once: { text: '一次性' }, }, render: (_, record) => ( {scheduleTypeLabels[record.schedule_type] || record.schedule_type} ), }, { title: '目标', dataIndex: ['target', 'type'], width: 140, hideInSearch: true, render: (_, record) => ( {targetTypeLabels[record.target.type] || record.target.type} {record.target.id} ), }, { title: '启用', dataIndex: 'enabled', width: 80, hideInSearch: true, render: (_, record) => ( toggleMutation.mutate({ id: record.id, enabled: checked })} /> ), }, { title: '执行次数', dataIndex: 'run_count', width: 90, hideInSearch: true, render: (_, record) => ( {record.run_count} ), }, { title: '上次执行', dataIndex: 'last_run', width: 170, hideInSearch: true, render: (_, record) => formatDateTime(record.last_run), }, { title: '下次执行', dataIndex: 'next_run', width: 170, hideInSearch: true, render: (_, record) => formatDateTime(record.next_run), }, { title: '上次耗时', dataIndex: 'last_duration_ms', width: 100, hideInSearch: true, render: (_, record) => formatDuration(record.last_duration_ms), }, { title: '上次错误', dataIndex: 'last_error', width: 160, ellipsis: true, hideInSearch: true, render: (_, record) => record.last_error ? ( {record.last_error} ) : ( - ), }, { title: '操作', width: 140, hideInSearch: true, render: (_, record) => ( deleteMutation.mutate(record.id)} > ), }, ] const openCreateModal = () => { setEditingId(null) form.resetFields() form.setFieldsValue({ schedule_type: 'cron', enabled: true }) setModalOpen(true) } const openEditModal = (record: ScheduledTask) => { setEditingId(record.id) form.setFieldsValue({ name: record.name, schedule: record.schedule, schedule_type: record.schedule_type, target_type: record.target.type, target_id: record.target.id, description: record.description ?? '', enabled: record.enabled, }) setModalOpen(true) } const closeModal = () => { setModalOpen(false) setEditingId(null) form.resetFields() } const handleSave = async () => { const values = await form.validateFields() const payload: CreateScheduledTaskRequest | UpdateScheduledTaskRequest = { name: values.name, schedule: values.schedule, schedule_type: values.schedule_type, target: { type: values.target_type, id: values.target_id, }, description: values.description || undefined, enabled: values.enabled, } if (editingId) { updateMutation.mutate({ id: editingId, data: payload }) } else { createMutation.mutate(payload as CreateScheduledTaskRequest) } } if (error) { return ( <> refetch()} /> ) } const tasks = Array.isArray(data) ? data : [] return (
} onClick={openCreateModal} > 新建任务 } /> columns={columns} dataSource={tasks} loading={isLoading} rowKey="id" search={false} toolBarRender={() => []} pagination={{ showSizeChanger: true, defaultPageSize: 20, }} options={{ density: false, fullScreen: false, reload: () => refetch(), }} /> {editingId ? '编辑任务' : '新建任务'} } open={modalOpen} onOk={handleSave} onCancel={closeModal} confirmLoading={createMutation.isPending || updateMutation.isPending} width={520} destroyOnHidden >
) }