diff --git a/ui/src/workflow-canvas/config/node-data.ts b/ui/src/workflow-canvas/config/node-data.ts index 36c37390568..f29479bd16c 100644 --- a/ui/src/workflow-canvas/config/node-data.ts +++ b/ui/src/workflow-canvas/config/node-data.ts @@ -394,7 +394,20 @@ export const loopNode = { y: 200, id: 'loop-start-node', type: 'loop-start-node', - properties: { config: { fields: [], globalFields: [] }, fields: [], showNode: true, stepName: '开始', globalFields: [] }, + properties: { + config: { + // 循环引擎内置的输出参数(index/item),只读展示,用户不可增删改 + fields: [ + { label: '下标', value: 'index' }, + { label: '循环元素', value: 'item' }, + ], + globalFields: [], + }, + fields: [], + showNode: true, + stepName: '开始', + globalFields: [], + }, }, ], }, @@ -410,6 +423,7 @@ export const loopStartNode = { properties: { stepName: '循环开始', config: { + // 循环引擎内置的输出参数(index/item),只读展示,用户不可增删改 fields: [ { label: '下标', value: 'index' }, { label: '循环元素', value: 'item' }, diff --git a/ui/src/workflow-canvas/nodes/loop-start-node/component/LoopFieldDialog.vue b/ui/src/workflow-canvas/nodes/loop-start-node/component/LoopFieldDialog.vue index f7c8b1295af..b333aa96582 100644 --- a/ui/src/workflow-canvas/nodes/loop-start-node/component/LoopFieldDialog.vue +++ b/ui/src/workflow-canvas/nodes/loop-start-node/component/LoopFieldDialog.vue @@ -28,6 +28,7 @@ import { reactive, ref } from 'vue' import { cloneDeep } from 'lodash' import type { FormInstance } from 'element-plus' +import { isLoopBuiltinField } from '../constant' defineOptions({ name: 'LoopFieldDialog' }) const emit = defineEmits<{ refresh: [data: { field: string; label: string }, index?: number] }>() @@ -42,6 +43,16 @@ const rules = reactive({ field: [ { required: true, message: '请输入变量名', trigger: 'blur' }, { pattern: /^[a-zA-Z0-9_]+$/, message: '变量名只能包含英文字母、数字、下划线', trigger: 'blur' }, + { + validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => { + if (isLoopBuiltinField(value)) { + callback(new Error('此变量名为循环引擎内置,不可使用')) + } else { + callback() + } + }, + trigger: 'blur', + }, ], }) diff --git a/ui/src/workflow-canvas/nodes/loop-start-node/component/LoopFieldTable.vue b/ui/src/workflow-canvas/nodes/loop-start-node/component/LoopFieldTable.vue index fbd0b24c2a5..e90f0265668 100644 --- a/ui/src/workflow-canvas/nodes/loop-start-node/component/LoopFieldTable.vue +++ b/ui/src/workflow-canvas/nodes/loop-start-node/component/LoopFieldTable.vue @@ -38,6 +38,7 @@ import { cloneDeep, set } from 'lodash' import type { BaseNodeModel } from '@logicflow/core' import LoopFieldDialog from './LoopFieldDialog.vue' import { MsgError } from '@/utils/message' +import { isLoopBuiltinField } from '../constant' defineOptions({ name: 'LoopFieldTable' }) const props = defineProps<{ nodeModel: BaseNodeModel }>() @@ -45,11 +46,6 @@ const props = defineProps<{ nodeModel: BaseNodeModel }>() type WorkflowGraphModel = BaseNodeModel['graphModel'] & { refresh_loop_fields?: (fields: Array<{ label: string; value: string }>) => void } type LoopInputField = { field: string; label: string } -const DEFAULT_FIELDS: LoopInputField[] = [ - { field: 'index', label: '下标' }, - { field: 'item', label: '循环元素' }, -] - const fieldDialogRef = ref>() const loopInputFieldList = ref([]) @@ -85,11 +81,10 @@ function sync() { onMounted(() => { const properties = props.nodeModel.properties as { loop_input_field_list?: LoopInputField[] } - if (Array.isArray(properties.loop_input_field_list)) { - loopInputFieldList.value = cloneDeep(properties.loop_input_field_list) - } else { - loopInputFieldList.value = cloneDeep(DEFAULT_FIELDS) - } + // 内置参数(index/item)由循环引擎提供,作为只读输出参数展示,不进入用户可配置的循环变量列表 + loopInputFieldList.value = (Array.isArray(properties.loop_input_field_list) ? cloneDeep(properties.loop_input_field_list) : []).filter( + (item) => !isLoopBuiltinField(item.field), + ) sync() }) diff --git a/ui/src/workflow-canvas/nodes/loop-start-node/constant.ts b/ui/src/workflow-canvas/nodes/loop-start-node/constant.ts new file mode 100644 index 00000000000..27604fcd11a --- /dev/null +++ b/ui/src/workflow-canvas/nodes/loop-start-node/constant.ts @@ -0,0 +1,8 @@ +// 循环引擎自带的输出参数(内置,界面上不展示、不可被用户修改) +export const LOOP_BUILTIN_FIELDS = [ + { field: 'index', label: '下标' }, + { field: 'item', label: '循环元素' }, +] as const + +export const isLoopBuiltinField = (value?: string) => + !!value && LOOP_BUILTIN_FIELDS.some((item) => item.field === value) diff --git a/ui/src/workflow-canvas/nodes/loop-start-node/index.ts b/ui/src/workflow-canvas/nodes/loop-start-node/index.ts index 0bdefed16d3..93bddae0f73 100644 --- a/ui/src/workflow-canvas/nodes/loop-start-node/index.ts +++ b/ui/src/workflow-canvas/nodes/loop-start-node/index.ts @@ -1,6 +1,7 @@ import LoopStartNodeVue from './index.vue' import { WorkflowNodeModel, WorkflowNodeView } from '@/workflow-canvas/core/workflow-node' import { WorkflowNodeType, type WorkflowNodeField } from '@/workflow-canvas/types' +import { isLoopBuiltinField, LOOP_BUILTIN_FIELDS } from './constant' interface LoopStartNodeProperties { config?: { fields?: Array<{ label: string; value: string }>; globalFields?: unknown[] } @@ -9,13 +10,24 @@ interface LoopStartNodeProperties { } class LoopStartNodeModel extends WorkflowNodeModel { + override setAttributes() { + super.setAttributes() + // 循环引擎内置的 index/item 作为只读输出参数,始终保留在节点自身配置里,保证输出区一定展示 + const config = (this.properties.config ?? {}) as { fields?: unknown[]; globalFields?: unknown[] } + this.properties.config = { + ...config, + fields: LOOP_BUILTIN_FIELDS.map((item) => ({ label: item.label, value: item.field })), + } + } + override getNodeFieldList(): WorkflowNodeField[] { const properties = this.properties as LoopStartNodeProperties - const inputFields = - properties.loop_input_field_list && properties.loop_input_field_list.length - ? properties.loop_input_field_list.map((item) => ({ field: item.field ?? item.variable ?? '', label: item.label ?? '' })) - : (properties.config?.fields ?? []).map((field) => ({ field: field.value, label: field.label })) - const loopFields = inputFields.map((item) => ({ label: item.label, value: item.field })).filter((field) => Boolean(field.value)) + const inputFields = properties.loop_input_field_list + ? properties.loop_input_field_list + : (properties.config?.fields ?? []).map((field) => ({ field: field.value, label: field.label, variable: undefined })) + const loopFields = inputFields + .map((item) => ({ label: item.label ?? '', value: item.field ?? item.variable ?? '' })) + .filter((field) => Boolean(field.value) && !isLoopBuiltinField(field.value)) return [{ value: 'loop', label: '循环变量', type: 'loop', children: loopFields }, ...super.getNodeFieldList()] } } diff --git a/ui/src/workflow-canvas/nodes/loop-start-node/index.vue b/ui/src/workflow-canvas/nodes/loop-start-node/index.vue index 0f3bf255068..dcb255f142d 100644 --- a/ui/src/workflow-canvas/nodes/loop-start-node/index.vue +++ b/ui/src/workflow-canvas/nodes/loop-start-node/index.vue @@ -25,6 +25,7 @@ import type { BaseNodeModel } from '@logicflow/core' import NodeContainer from '@/workflow-canvas/core/node-container/index.vue' import LoopFieldTable from './component/LoopFieldTable.vue' import { copyText } from '@/utils/clipboard' +import { isLoopBuiltinField } from './constant' defineOptions({ name: 'WorkflowLoopStartNode' }) const getModel = inject('getModel') as () => BaseNodeModel @@ -40,6 +41,6 @@ const loopInputFields = computed(() => { const list = (model.properties as { loop_input_field_list?: LoopInputField[] }).loop_input_field_list ?? [] return list .map((item) => ({ label: item.label ?? item.field ?? '', value: item.field ?? item.variable ?? '' })) - .filter((field) => Boolean(field.value)) + .filter((field) => Boolean(field.value) && !isLoopBuiltinField(field.value)) })