diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json index 2e0261a18..f9bc02d7c 100644 --- a/frontend/src/i18n/locales/en/common.json +++ b/frontend/src/i18n/locales/en/common.json @@ -82,17 +82,27 @@ "actions": { "start": "Start", "stop": "Stop", + "retry": "Retry", + "edit": "Edit", "executionRecords": "Execution Records", "delete": "Delete" }, "messages": { "startSuccess": "Task start request sent", "stopSuccess": "Task stop request sent", + "retrySuccess": "Task retry request sent", + "updateSuccess": "Task updated successfully", + "updateFailed": "Failed to update task", "deleteSuccess": "Task deleted", "deleteConfirm": "Are you sure you want to delete this task? This action cannot be undone.", "deleteConfirmMessage": "Are you sure you want to delete task \"{{taskName}}\"? This action cannot be undone.", "confirmDelete": "Delete", "cancel": "Cancel" + }, + "edit": { + "title": "Edit Task", + "taskInfo": "Task Information", + "resetHint": "After saving, the task status will be reset to PENDING and can be re-executed" } }, "templateManagement": { @@ -165,9 +175,11 @@ }, "createTask": { "title": "Create Collection Task", + "editTitle": "Edit Collection Task", "back": "Back", "cancel": "Cancel", "submit": "Create Task", + "updateButton": "Update Task", "actions": { "formatJson": "Format JSON" }, diff --git a/frontend/src/i18n/locales/zh/common.json b/frontend/src/i18n/locales/zh/common.json index fa200d1b0..2edef769e 100644 --- a/frontend/src/i18n/locales/zh/common.json +++ b/frontend/src/i18n/locales/zh/common.json @@ -82,17 +82,27 @@ "actions": { "start": "启动", "stop": "停止", + "retry": "重试", + "edit": "编辑", "executionRecords": "执行记录", "delete": "删除" }, "messages": { "startSuccess": "任务启动请求已发送", "stopSuccess": "任务停止请求已发送", + "retrySuccess": "任务重试请求已发送", + "updateSuccess": "任务更新成功", + "updateFailed": "任务更新失败", "deleteSuccess": "任务已删除", "deleteConfirm": "确定要删除该任务吗?此操作不可撤销。", "deleteConfirmMessage": "确定要删除任务「{{taskName}}」吗?删除后将无法恢复。", "confirmDelete": "删除", "cancel": "取消" + }, + "edit": { + "title": "编辑任务", + "taskInfo": "任务信息", + "resetHint": "保存后任务状态将重置为待执行,可以重新执行" } }, "templateManagement": { @@ -165,9 +175,11 @@ }, "createTask": { "title": "创建归集任务", + "editTitle": "编辑归集任务", "back": "返回", "cancel": "取消", "submit": "创建任务", + "updateButton": "更新任务", "actions": { "formatJson": "格式化JSON" }, diff --git a/frontend/src/pages/DataCollection/Create/CreateTask.tsx b/frontend/src/pages/DataCollection/Create/CreateTask.tsx index 8134e9924..a3f97ddaf 100644 --- a/frontend/src/pages/DataCollection/Create/CreateTask.tsx +++ b/frontend/src/pages/DataCollection/Create/CreateTask.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import { Input, Button, Radio, Form, App, Select, InputNumber } from "antd"; -import { Link, useNavigate } from "react-router"; +import { Link, useNavigate, useSearchParams } from "react-router"; import { ArrowLeft } from "lucide-react"; -import { createTaskUsingPost, queryDataXTemplatesUsingGet } from "../collection.apis"; +import { createTaskUsingPost, updateTaskUsingPut, queryDataXTemplatesUsingGet, queryTasksUsingGet } from "../collection.apis"; import SimpleCronScheduler from "@/pages/DataCollection/Create/SimpleCronScheduler"; import { getSyncModeMap } from "../collection.const"; import { SyncMode } from "../collection.model"; @@ -39,11 +39,17 @@ type TemplateFieldDef = { export default function CollectionTaskCreate() { const navigate = useNavigate(); + const [searchParams] = useSearchParams(); const [form] = Form.useForm(); const { message } = App.useApp(); const { t } = useTranslation(); const syncModeOptions = Object.values(getSyncModeMap(t)); + // 编辑模式 + const taskId = searchParams.get("taskId"); + const isEditMode = !!taskId; + const [editLoading, setEditLoading] = useState(false); + const [templates, setTemplates] = useState([]); const [templatesLoading, setTemplatesLoading] = useState(false); const [selectedTemplateId, setSelectedTemplateId] = useState(undefined); @@ -65,8 +71,52 @@ export default function CollectionTaskCreate() { cronExpression: "0 0 * * *", }); + // 解析 cron 表达式 + const parseCronExpression = (cronExpr: string) => { + const parts = cronExpr.trim().split(/\s+/); + if (parts.length !== 5) { + // 无效的 cron 表达式,返回默认值 + return { + type: "daily" as const, + time: "00:00", + cronExpression: cronExpr, + }; + } + + const [minute, hour, day, month, weekday] = parts; + const formattedHour = hour.padStart(2, "0"); + const formattedMinute = minute.padStart(2, "0"); + const time = `${formattedHour}:${formattedMinute}`; + + // 判断类型:monthly (指定日期), weekly (指定星期), daily (都是 *) + if (day !== "*" && month === "*") { + // monthly: 例如 "0 9 1 * *" 表示每月1号9点 + return { + type: "monthly" as const, + time, + monthDay: parseInt(day, 10), + cronExpression: cronExpr, + }; + } else if (weekday !== "*" && day === "*") { + // weekly: 例如 "0 9 * * 1" 表示每周一9点 + return { + type: "weekly" as const, + time, + weekDay: parseInt(weekday, 10), + cronExpression: cronExpr, + }; + } else { + // daily: 例如 "0 9 * * *" 表示每天9点 + return { + type: "daily" as const, + time, + cronExpression: cronExpr, + }; + } + }; + useEffect(() => { - const run = async () => { + const loadTemplates = async () => { setTemplatesLoading(true); try { const resp: any = await queryDataXTemplatesUsingGet({ page: 1, size: 1000 }); @@ -78,8 +128,56 @@ export default function CollectionTaskCreate() { setTemplatesLoading(false); } }; - run() - }, []); + + const loadTask = async () => { + if (!taskId) return; + setEditLoading(true); + try { + const resp: any = await queryTasksUsingGet({ page: 1, size: 1 }); + const task = resp?.data?.content?.find((t: any) => t.id === taskId); + if (task) { + // 设置表单值 + setSelectedTemplateId(task.templateId); + form.setFieldsValue({ + name: task.name, + description: task.description, + syncMode: task.syncMode, + scheduleExpression: task.scheduleExpression || "", + timeoutSeconds: task.timeoutSeconds || 3600, + templateId: task.templateId, + config: task.config || { parameter: {}, reader: {}, writer: {} }, + }); + setNewTask({ + name: task.name, + description: task.description, + syncMode: task.syncMode, + scheduleExpression: task.scheduleExpression || "", + timeoutSeconds: task.timeoutSeconds || 3600, + templateId: task.templateId, + config: task.config || { parameter: {}, reader: {}, writer: {} }, + }); + // 解析 cron 表达式 + if (task.scheduleExpression) { + const parsedSchedule = parseCronExpression(task.scheduleExpression); + setScheduleExpression(parsedSchedule); + } + } else { + message.error(t("dataCollection.taskManagement.messages.updateFailed")); + navigate("/data/collection"); + } + } catch (e) { + message.error(t("dataCollection.taskManagement.messages.updateFailed")); + navigate("/data/collection"); + } finally { + setEditLoading(false); + } + }; + + loadTemplates(); + if (isEditMode) { + loadTask(); + } + }, [taskId]); const parseJsonObjectInput = (value: any) => { if (value === undefined || value === null) return value; @@ -195,10 +293,30 @@ export default function CollectionTaskCreate() { ), }; } - await createTaskUsingPost(payload); - message.success(t("dataCollection.createTask.messages.createSuccess")); + + if (isEditMode) { + // 编辑模式:只更新允许的字段 + const updateData: any = { + description: payload.description, + timeoutSeconds: payload.timeoutSeconds, + config: payload.config, + }; + if (payload.syncMode === SyncMode.SCHEDULED && payload.scheduleExpression) { + updateData.scheduleExpression = payload.scheduleExpression; + } + await updateTaskUsingPut(taskId!, updateData); + message.success(t("dataCollection.taskManagement.messages.updateSuccess")); + } else { + // 创建模式 + await createTaskUsingPost(payload); + message.success(t("dataCollection.createTask.messages.createSuccess")); + } navigate("/data/collection"); } catch (error) { + if (error.errorFields) { + // 表单验证错误,不显示消息 + return; + } message.error( t("dataCollection.createTask.messages.errorWithDetail", { message: error?.data?.message ?? "", @@ -412,22 +530,30 @@ export default function CollectionTaskCreate() { return (
-
-
- - - -

- {t("dataCollection.createTask.title")} -

+ {editLoading ? ( +
+
{t("common.loading")}
-
+ ) : ( + <> +
+
+ + + +

+ {isEditMode + ? t("dataCollection.createTask.editTitle") + : t("dataCollection.createTask.title")} +

+
+
-
-
-
+
+ - + { const value = e.target.value; setNewTask({ @@ -532,6 +662,7 @@ export default function CollectionTaskCreate() {