修复任务参数类型转换

This commit is contained in:
ryan
2026-06-11 08:40:08 +08:00
parent 8964054fd8
commit 5dedff1324
8 changed files with 46 additions and 8 deletions
@@ -6,6 +6,7 @@ import {Button} from "@/components/ui/button"
import {Input} from "@/components/ui/input"
import {Label} from "@/components/ui/label"
import {Textarea} from "@/components/ui/textarea"
import {Switch} from "@/components/ui/switch"
import {Spinner} from "@/components/ui/spinner"
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog"
import {Calendar as CalendarIcon, Clock, Info, Layers, Play} from "lucide-react"
@@ -380,6 +381,17 @@ export function TaskManager() {
value={paramValues[param.name] || ""}
onChange={(e) => setParamValues(prev => ({ ...prev, [param.name]: e.target.value }))}
/>
) : param.type === 'boolean' ? (
<div className="flex items-center gap-2 pt-1 h-9">
<Switch
id={`param-${param.name}`}
checked={paramValues[param.name] === 'true'}
onCheckedChange={(checked) => setParamValues(prev => ({ ...prev, [param.name]: checked ? 'true' : 'false' }))}
/>
<span className="text-xs text-muted-foreground">
{paramValues[param.name] === 'true' ? '开启' : '关闭'}
</span>
</div>
) : (
<Input
id={`param-${param.name}`}
@@ -404,6 +404,17 @@ export function TaskSchedulesManager() {
value={paramValues[param.name] || ""}
onChange={(e) => setParamValues(prev => ({ ...prev, [param.name]: e.target.value }))}
/>
) : param.type === 'boolean' ? (
<div className="flex items-center gap-2 pt-1 h-9">
<Switch
id={`param-${param.name}`}
checked={paramValues[param.name] === 'true'}
onCheckedChange={(checked) => setParamValues(prev => ({ ...prev, [param.name]: checked ? 'true' : 'false' }))}
/>
<span className="text-xs text-muted-foreground">
{paramValues[param.name] === 'true' ? '开启' : '关闭'}
</span>
</div>
) : (
<Input
id={`param-${param.name}`}
+2 -1
View File
@@ -97,8 +97,9 @@ export interface TaskTypeResponse {
* - string → 单行文本输入,JSON 中序列化为 string
* - text → 多行文本输入,JSON 中序列化为 string
* - number → 数字输入,JSON 中序列化为 number(而非 string)
* - boolean → 开关输入,JSON 中序列化为 boolean(而非 string)
*/
export type TaskParamType = 'string' | 'text' | 'number';
export type TaskParamType = 'string' | 'text' | 'number' | 'boolean';
/**
* 任务参数定义
+16 -2
View File
@@ -32,7 +32,7 @@ export interface BuildPayloadResult {
function coerceParamValue(
param: TaskParam,
rawValue: string,
): { value: string | number; ok: true } | { value: undefined; ok: true } | { error: string; ok: false } {
): { value: string | number | boolean; ok: true } | { value: undefined; ok: true } | { error: string; ok: false } {
const trimmed = rawValue.trim();
switch (param.type) {
@@ -47,6 +47,19 @@ function coerceParamValue(
}
return { value: num, ok: true };
}
case 'boolean': {
if (trimmed === '') {
// 空值 → 省略该字段
return { value: undefined, ok: true };
}
const val = trimmed.toLowerCase();
if (val === 'true') {
return { value: true, ok: true };
} else if (val === 'false') {
return { value: false, ok: true };
}
return { error: `「${param.label}」必须是 true 或 false`, ok: false };
}
case 'string':
case 'text':
default:
@@ -62,6 +75,7 @@ function coerceParamValue(
* - `number` 类型参数:序列化为 JSON number,而非 JSON string。
* - `number` 类型参数为空且非必填:从 payload 中省略该字段(不发送),
* 避免后端收到 `"target_type":""` 再尝试解析为 int 时报错。
* - `boolean` 类型参数:序列化为 JSON boolean。
* - `required` 字段为空:返回 error。
*
* @param params 任务的参数定义列表(来自 TaskMeta.params)
@@ -72,7 +86,7 @@ export function buildTaskPayload(
params: TaskParam[],
values: Record<string, string>,
): BuildPayloadResult {
const payloadData: Record<string, string | number> = {};
const payloadData: Record<string, string | number | boolean> = {};
for (const param of params) {
const raw = values[param.name] ?? '';
+1 -1
View File
@@ -187,7 +187,7 @@ func RetryTask(c *gin.Context) {
switch {
case strings.Contains(errMsg, "不存在"):
c.JSON(http.StatusNotFound, util.Err(errMsg))
case strings.Contains(errMsg, "只有非进行中") || strings.Contains(errMsg, "不支持重试") || strings.Contains(errMsg, "已达到最大重试"):
case strings.Contains(errMsg, "只有失败的任务") || strings.Contains(errMsg, "不支持重试") || strings.Contains(errMsg, "已达到最大重试"):
c.JSON(http.StatusBadRequest, util.Err(errMsg))
default:
c.JSON(http.StatusInternalServerError, util.Err(fmt.Sprintf("%s: %v", TaskRetryFailed, err)))
+1 -1
View File
@@ -31,7 +31,7 @@ const defaultMaxRetry = 3
type TaskParam struct {
Name string `json:"Name"` // 参数键名
Label string `json:"Label"` // 显示名称
Type string `json:"Type"` // 类型:string, text, number
Type string `json:"Type"` // 类型:string, text, number, boolean
Required bool `json:"Required"` // 是否必填
Placeholder string `json:"Placeholder"` // 占位符
Description string `json:"Description"` // 描述
+1 -1
View File
@@ -8,7 +8,7 @@ const (
errCreateTaskExecutionFailed = "创建任务执行记录失败: %w"
errTaskEnqueueFailed = "任务入队失败: %w"
errTaskExecutionNotFound = "任务执行记录不存在: %w"
errRetryOnlyFinishedTask = "只有非进行中的任务才能重试,当前状态: %s"
errRetryOnlyFailedTask = "只有失败的任务才能重试,当前状态: %s"
errTaskNotRetryable = "该任务不支持重试"
errTaskMaxRetryExceeded = "已达到最大重试次数 %d"
errCreateRetryExecutionFailed = "创建重试任务执行记录失败: %w"
+2 -2
View File
@@ -138,8 +138,8 @@ func RetryTask(ctx context.Context, id uint64) (string, error) {
return "", fmt.Errorf(errTaskExecutionNotFound, err)
}
if execution.Status == model.TaskExecutionStatusPending || execution.Status == model.TaskExecutionStatusRunning {
return "", fmt.Errorf(errRetryOnlyFinishedTask, execution.Status)
if execution.Status != model.TaskExecutionStatusFailed {
return "", fmt.Errorf(errRetryOnlyFailedTask, execution.Status)
}
if !execution.Retryable {