From e7b914a2ae5fb06c7cf776a2899ec6eb1a3e55b2 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Wed, 22 Oct 2025 14:40:54 +0800 Subject: [PATCH] 1 --- frontend/src/pages/Workflow/Design/index.tsx | 15 +- .../Design/nodes/NotificationNode.tsx | 5 +- frontend/src/pages/Workflow/Design/types.ts | 1 + .../Workflow/Design/utils/schemaConverter.ts | 149 ++++++++++-------- 4 files changed, 96 insertions(+), 74 deletions(-) diff --git a/frontend/src/pages/Workflow/Design/index.tsx b/frontend/src/pages/Workflow/Design/index.tsx index 82e88cb6..dda93eb0 100644 --- a/frontend/src/pages/Workflow/Design/index.tsx +++ b/frontend/src/pages/Workflow/Design/index.tsx @@ -68,7 +68,7 @@ const WorkflowDesignInner: React.FC = () => { if (data) { setNodes(data.nodes); setEdges(data.edges); - setWorkflowTitle(data.definition.name); + setWorkflowTitle(data.definition?.name || '未命名工作流'); } } }; @@ -419,11 +419,16 @@ const WorkflowDesignInner: React.FC = () => { tagName === 'TEXTAREA' || target.isContentEditable || target.getAttribute('contenteditable') === 'true'; - const isInDrawer = target.closest('.ant-drawer-body') !== null; - const isInModal = target.closest('.ant-modal') !== null; - // 在抽屉或模态框内,且在输入元素中时,允许原生行为 - const shouldSkipShortcut = isInputElement || isInDrawer || isInModal; + // 检查是否在 shadcn/ui Sheet、Dialog 或其他模态框内 + const isInSheet = target.closest('[role="dialog"]') !== null; + const isInModal = target.closest('[role="alertdialog"]') !== null; + const isInPopover = target.closest('[role="menu"]') !== null || + target.closest('[role="listbox"]') !== null || + target.closest('[data-radix-popper-content-wrapper]') !== null; + + // 在模态框或弹窗内,或者在输入元素中时,允许原生行为 + const shouldSkipShortcut = isInputElement || isInSheet || isInModal || isInPopover; const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; const ctrlKey = isMac ? e.metaKey : e.ctrlKey; diff --git a/frontend/src/pages/Workflow/Design/nodes/NotificationNode.tsx b/frontend/src/pages/Workflow/Design/nodes/NotificationNode.tsx index 133fa2bb..e9ae1c48 100644 --- a/frontend/src/pages/Workflow/Design/nodes/NotificationNode.tsx +++ b/frontend/src/pages/Workflow/Design/nodes/NotificationNode.tsx @@ -83,7 +83,7 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = { default: "" } }, - required: ["nodeName", "nodeCode", "notificationType", "title", "content", "recipients"] + required: ["nodeName", "nodeCode", "notificationType", "title", "content"] }, outputs: [{ name: "status", @@ -91,8 +91,7 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = { type: "string", enum: ["SUCCESS", "FAILURE"], description: "执行的结果状态", - example: "SUCCESS", - required: true + example: "SUCCESS" }] }; diff --git a/frontend/src/pages/Workflow/Design/types.ts b/frontend/src/pages/Workflow/Design/types.ts index 4c8e7fd4..344c8278 100644 --- a/frontend/src/pages/Workflow/Design/types.ts +++ b/frontend/src/pages/Workflow/Design/types.ts @@ -9,6 +9,7 @@ export enum NodeType { SCRIPT_TASK = 'SCRIPT_TASK', DEPLOY_NODE = 'DEPLOY_NODE', JENKINS_BUILD = 'JENKINS_BUILD', + NOTIFICATION = 'NOTIFICATION', GATEWAY_NODE = 'GATEWAY_NODE', SUB_PROCESS = 'SUB_PROCESS', CALL_ACTIVITY = 'CALL_ACTIVITY' diff --git a/frontend/src/pages/Workflow/Design/utils/schemaConverter.ts b/frontend/src/pages/Workflow/Design/utils/schemaConverter.ts index 0c507eaa..da294bd2 100644 --- a/frontend/src/pages/Workflow/Design/utils/schemaConverter.ts +++ b/frontend/src/pages/Workflow/Design/utils/schemaConverter.ts @@ -15,77 +15,94 @@ export const convertJsonSchemaToZod = (jsonSchema: JSONSchema): z.ZodObject Object.entries(jsonSchema.properties).forEach(([key, prop]: [string, any]) => { let fieldSchema: z.ZodTypeAny; + const isRequired = Array.isArray(jsonSchema.required) && jsonSchema.required.includes(key); - // 根据类型创建 Zod Schema - switch (prop.type) { - case 'string': - fieldSchema = z.string(); - if (prop.format === 'email') { - fieldSchema = (fieldSchema as z.ZodString).email(prop.title ? `${prop.title}格式不正确` : '邮箱格式不正确'); - } else if (prop.format === 'url') { - fieldSchema = (fieldSchema as z.ZodString).url(prop.title ? `${prop.title}格式不正确` : 'URL格式不正确'); - } - if (prop.minLength) { - fieldSchema = (fieldSchema as z.ZodString).min(prop.minLength, `${prop.title || key}至少需要${prop.minLength}个字符`); - } - if (prop.maxLength) { - fieldSchema = (fieldSchema as z.ZodString).max(prop.maxLength, `${prop.title || key}最多${prop.maxLength}个字符`); - } - if (prop.pattern) { - fieldSchema = (fieldSchema as z.ZodString).regex(new RegExp(prop.pattern), `${prop.title || key}格式不正确`); - } - break; - - case 'number': - case 'integer': - fieldSchema = prop.type === 'integer' ? z.number().int() : z.number(); - if (prop.minimum !== undefined) { - fieldSchema = (fieldSchema as z.ZodNumber).min(prop.minimum, `${prop.title || key}不能小于${prop.minimum}`); - } - if (prop.maximum !== undefined) { - fieldSchema = (fieldSchema as z.ZodNumber).max(prop.maximum, `${prop.title || key}不能大于${prop.maximum}`); - } - break; - - case 'boolean': - fieldSchema = z.boolean(); - break; - - case 'array': - fieldSchema = z.array(z.any()); - if (prop.minItems !== undefined) { - fieldSchema = (fieldSchema as z.ZodArray).min(prop.minItems, `${prop.title || key}至少需要${prop.minItems}项`); - } - if (prop.maxItems !== undefined) { - fieldSchema = (fieldSchema as z.ZodArray).max(prop.maxItems, `${prop.title || key}最多${prop.maxItems}项`); - } - break; - - case 'object': - fieldSchema = z.record(z.any()); - break; - - default: - fieldSchema = z.any(); - } - - // 处理默认值 - if (prop.default !== undefined) { - fieldSchema = fieldSchema.default(prop.default); - } - - // 处理枚举 + // 如果有枚举,直接使用枚举(枚举会覆盖其他类型) if (prop.enum && Array.isArray(prop.enum)) { const enumValues = prop.enum.map((v: any) => (typeof v === 'object' ? v.value : v)); fieldSchema = z.enum(enumValues as [string, ...string[]]); - } - - // 处理必填字段 - if (Array.isArray(jsonSchema.required) && jsonSchema.required.includes(key)) { - // 已经是必填的,不需要额外处理 + + // 处理默认值 + if (prop.default !== undefined) { + fieldSchema = fieldSchema.default(prop.default); + } + + // 处理必填/可选 + if (!isRequired) { + fieldSchema = fieldSchema.optional(); + } } else { - // 非必填字段设为 optional - fieldSchema = fieldSchema.optional(); + // 根据类型创建 Zod Schema + switch (prop.type) { + case 'string': { + let stringSchema = z.string(); + + // 必填字段需要非空验证 + if (isRequired) { + stringSchema = stringSchema.min(1, `${prop.title || key}不能为空`); + } + + if (prop.format === 'email') { + stringSchema = stringSchema.email(prop.title ? `${prop.title}格式不正确` : '邮箱格式不正确'); + } else if (prop.format === 'url') { + stringSchema = stringSchema.url(prop.title ? `${prop.title}格式不正确` : 'URL格式不正确'); + } + if (prop.minLength) { + stringSchema = stringSchema.min(prop.minLength, `${prop.title || key}至少需要${prop.minLength}个字符`); + } + if (prop.maxLength) { + stringSchema = stringSchema.max(prop.maxLength, `${prop.title || key}最多${prop.maxLength}个字符`); + } + if (prop.pattern) { + stringSchema = stringSchema.regex(new RegExp(prop.pattern), `${prop.title || key}格式不正确`); + } + + fieldSchema = stringSchema; + break; + } + + case 'number': + case 'integer': + fieldSchema = prop.type === 'integer' ? z.number().int() : z.number(); + if (prop.minimum !== undefined) { + fieldSchema = (fieldSchema as z.ZodNumber).min(prop.minimum, `${prop.title || key}不能小于${prop.minimum}`); + } + if (prop.maximum !== undefined) { + fieldSchema = (fieldSchema as z.ZodNumber).max(prop.maximum, `${prop.title || key}不能大于${prop.maximum}`); + } + break; + + case 'boolean': + fieldSchema = z.boolean(); + break; + + case 'array': + fieldSchema = z.array(z.any()); + if (prop.minItems !== undefined) { + fieldSchema = (fieldSchema as z.ZodArray).min(prop.minItems, `${prop.title || key}至少需要${prop.minItems}项`); + } + if (prop.maxItems !== undefined) { + fieldSchema = (fieldSchema as z.ZodArray).max(prop.maxItems, `${prop.title || key}最多${prop.maxItems}项`); + } + break; + + case 'object': + fieldSchema = z.record(z.any()); + break; + + default: + fieldSchema = z.any(); + } + + // 处理默认值 + if (prop.default !== undefined) { + fieldSchema = fieldSchema.default(prop.default); + } + + // 处理可选字段 + if (!isRequired) { + fieldSchema = fieldSchema.optional(); + } } shape[key] = fieldSchema;