From 9959f95b9415f3be99a558b899ff22c8b7985669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Thu, 30 Jul 2026 16:23:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=98=B2=E6=AD=A2=E5=BC=80=E5=A7=8B?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E9=87=8D=E5=90=8D=E5=8F=82=E6=95=B0=E8=A2=AB?= =?UTF-8?q?=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 校验开始节点参数名并保留冲突输入 - 在字段归一化前拦截重名并补充回归测试 --- .../core/DefinedParameterItem.svelte | 53 +++++++++++++++++-- .../src/utils/workflowNodeFields.test.ts | 42 +++++++++++++++ .../src/utils/workflowNodeFields.ts | 36 +++++++++++++ 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/DefinedParameterItem.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/DefinedParameterItem.svelte index 64ff55cc..a5c94338 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/DefinedParameterItem.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/core/DefinedParameterItem.svelte @@ -7,6 +7,7 @@ import {useTinyflowStore} from '#store/stores.svelte'; import {getCurrentNodeId} from '#components/utils/NodeUtils'; import { + isStartFormFieldKeyAvailable, renameStartFieldReferencesInNodes, removeStartFormField, START_NODE_TYPE, @@ -47,12 +48,15 @@ } return param.formType ? [param.formType] : []; }); + let parameterNameDraft = $state(null); let displayParamName = $derived.by(() => { if (isSystemStartParam) { return '用户问题'; } - return param.name; + return parameterNameDraft ?? param.name; }); + let parameterNameError = $state(''); + let parameterNameErrorId = $derived(`parameter-name-error-${param.id || index}`); const { updateNodeData } = useSvelteFlow(); @@ -146,7 +150,27 @@ }; const updateName = (event: Event) => { - const newValue = (event.target as any).value; + const input = event.target as HTMLInputElement; + const newValue = input.value; + if (isStartNodeInputParam) { + const normalizedValue = trimString(newValue); + if (!normalizedValue) { + parameterNameError = '参数名不能为空'; + parameterNameDraft = newValue; + return; + } + if (!isStartFormFieldKeyAvailable( + node?.current?.data as Record, + param.name || '', + normalizedValue + )) { + parameterNameError = '参数名已存在'; + parameterNameDraft = newValue; + return; + } + } + parameterNameDraft = null; + parameterNameError = ''; updateParameter('name', newValue); }; @@ -191,10 +215,17 @@ -
+
+ {#if parameterNameError} + + {/if}
@@ -276,6 +307,22 @@ align-items: center; } + .input-item-name { + flex-direction: column; + align-items: stretch; + gap: 3px; + } + + .input-error { + font-size: 12px; + line-height: 1.4; + color: var(--tf-danger-soft-text); + } + + :global(.tf-input[aria-invalid='true']) { + border-color: var(--tf-danger-soft-border); + } + .input-more-setting { display: flex; flex-direction: column; diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.test.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.test.ts index 7b625465..36c82a41 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.test.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.test.ts @@ -13,6 +13,7 @@ import { createInitialWorkflowData, ensureStartNodeParameters, FIELD_BINDING_META_KEY, + isStartFormFieldKeyAvailable, normalizeStartNodeData, normalizeWorkflowStartNodes, renameStartFieldReferencesInNodes, @@ -148,6 +149,47 @@ describe('workflow node fields', () => { expect(nextParameter?.id).toBe(previousParameter?.id); }); + it('keeps start fields unchanged when renaming to an existing key', () => { + const initial = createInitialWorkflowData(); + const withTopic = appendStartFormField( + initial.nodes[0]?.data as Record, + { + key: 'topic', + label: '主题', + type: 'text', + }, + ); + const withDetails = appendStartFormField(withTopic, { + key: 'details', + label: '详情', + type: 'textarea', + }); + const previousFields = withDetails.startFormSchema; + const previousParameters = withDetails.parameters; + + expect( + isStartFormFieldKeyAvailable(withDetails, 'details', 'summary'), + ).toBe(true); + expect( + isStartFormFieldKeyAvailable(withDetails, 'details', 'topic'), + ).toBe(false); + expect( + isStartFormFieldKeyAvailable(withDetails, 'details', 'user_input'), + ).toBe(false); + + const duplicatedCustomKey = updateStartFormField(withDetails, 'details', { + key: 'topic', + }); + const duplicatedSystemKey = updateStartFormField(withDetails, 'details', { + key: 'user_input', + }); + + expect(duplicatedCustomKey.startFormSchema).toEqual(previousFields); + expect(duplicatedCustomKey.parameters).toEqual(previousParameters); + expect(duplicatedSystemKey.startFormSchema).toEqual(previousFields); + expect(duplicatedSystemKey.parameters).toEqual(previousParameters); + }); + it('renames downstream token and managed references when start field key changes', () => { const initialStartData = appendStartFormField( createInitialWorkflowData().nodes[0]?.data as Record, diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.ts index 35d4b63c..a505fe96 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/workflowNodeFields.ts @@ -720,6 +720,36 @@ export function normalizeStartFormSchema( return result; } +export function isStartFormFieldKeyAvailable( + data: Record | null | undefined, + currentKey: string, + candidateKey: string, +) { + const normalizedCurrentKey = trimString(currentKey); + const normalizedCandidateKey = trimString(candidateKey); + if (!normalizedCurrentKey || !normalizedCandidateKey) { + return false; + } + + const currentData = (data || {}) as Record; + const currentParameters = Array.isArray(currentData.parameters) + ? (currentData.parameters as Parameter[]) + : []; + const schema = normalizeStartFormSchema( + currentData.startFormSchema, + currentParameters, + ); + if (!schema.some((field) => field.key === normalizedCurrentKey)) { + return false; + } + + return !schema.some( + (field) => + field.key === normalizedCandidateKey && + field.key !== normalizedCurrentKey, + ); +} + export function createCustomStartFormField( field?: Partial | null, existingKeys: string[] = [], @@ -773,6 +803,12 @@ export function updateStartFormField( ? (currentData.parameters as Parameter[]) : []; const schema = normalizeStartFormSchema(currentData.startFormSchema, currentParameters); + if ( + patch.key !== undefined && + !isStartFormFieldKeyAvailable(currentData, currentKey, patch.key) + ) { + return normalizeStartNodeData(currentData); + } const nextSchema = schema.map((field) => { if (field.key !== currentKey) { return field;