setTemplatePrimitive(item, v)" size="small" />
@@ -71,12 +73,22 @@
class="mf-leaf-expose"
>表单展示
-
-
+
+
-
已引用:{{ valueSourceLabel }}
-
清除
+
+ {{ ref.label }}
+
+
清空
();
+const props = defineProps<{ fieldDef: any; path?: string; upstreamNodes?: UpstreamNodeInfo[]; multi?: boolean }>();
// 用 computed 实时取当前 props,避免 internalSync 替换对象树后仍持有旧引用
const def = computed(() => props.fieldDef);
@@ -167,37 +179,56 @@ const isUpload = computed(() => isLeaf.value && fieldType.value === 'upload');
// ===== 引用上级节点输出 =====
// 引用按钮与候选面板在 MfReferenceButton 中统一渲染(叶子与对象/数组整体均可引用)
-const hasValueSource = computed(() => !!def.value?.valueSource);
-// 已引用时的展示文案:{节点label}.{字段label}
-const valueSourceLabel = computed(() => {
- const ref = def.value?.valueSource;
- if (!ref) return '';
- const node = props.upstreamNodes?.find((n) => n.id === ref.nodeId);
- const nodeName = node?.label || ref.nodeId;
- const fieldLabel = node?.outputFields.find((f) => f.field === ref.field)?.label || ref.field;
- return `${nodeName}.${fieldLabel}`;
+// valueSource 契约统一数组 [{ nodeId, field }];multi(节点 isMultiParameter)时支持多选
+const valueSourceList = computed(() => {
+ const vs = def.value?.valueSource;
+ // 兼容旧 DSL 单对象 { nodeId, field }:统一按数组读取
+ if (!vs || typeof vs !== 'object') return [];
+ const arr = Array.isArray(vs) ? vs : [vs];
+ return arr.map((r: any) => {
+ const node = props.upstreamNodes?.find((n) => n.id === r?.nodeId);
+ const nodeName = node?.label || r?.nodeId || '';
+ const fieldLabel = node?.outputFields.find((f) => f.field === r?.field)?.label || r?.field || '';
+ const invalid = !node || !node.outputFields.some((f) => f.field === r?.field);
+ return { nodeId: r?.nodeId, field: r?.field, label: `${nodeName}.${fieldLabel}`, invalid };
+ });
});
+const hasValueSource = computed(() => valueSourceList.value.length > 0);
+// 任一引用失效即整体标红(失效项逐条展示,由失效清理 watch 自动移除)
+const valueSourceInvalid = computed(() => valueSourceList.value.some((r) => r.invalid));
-// 引用是否已失效:上游节点被删除,或其输出字段已不存在(换模型/换配置导致 schema 变化)
-const valueSourceInvalid = computed(() => {
- const ref = def.value?.valueSource;
- if (!ref) return false;
- const node = props.upstreamNodes?.find((n) => n.id === ref.nodeId);
- if (!node) return true;
- return !node.outputFields.some((f) => f.field === ref.field);
-});
-
-// 失效引用自动清理,避免保存脏数据(就地删除 valueSource,复用深 watch 上传链路)
+// 失效引用自动清理,避免保存脏数据(过滤失效项;全部失效则删除整个 valueSource,复用深 watch 上传链路)
watch(
- valueSourceInvalid,
- (invalid) => {
- if (invalid && def.value) {
- delete def.value.valueSource;
- }
+ () => valueSourceList.value,
+ (list) => {
+ if (!def.value) return;
+ const vs = def.value.valueSource;
+ if (!vs || typeof vs !== 'object') return;
+ // 兼容旧 DSL 单对象:按数组处理,清理后统一写回数组
+ const arr = Array.isArray(vs) ? vs : [vs];
+ if (!list.some((r) => r.invalid)) return;
+ const keep = arr.filter((r: any) => {
+ const node = props.upstreamNodes?.find((n) => n.id === r?.nodeId);
+ return node && node.outputFields.some((f) => f.field === r?.field);
+ });
+ if (keep.length === 0) delete def.value.valueSource;
+ else def.value.valueSource = keep;
},
{ immediate: true }
);
+// 删除单个引用(标签 closable)
+const removeValueSource = (ref: any) => {
+ if (!def.value) return;
+ const vs = def.value.valueSource;
+ if (!vs || typeof vs !== 'object') return;
+ const arr = Array.isArray(vs) ? vs : [vs];
+ const keep = arr.filter((r: any) => !(r?.nodeId === ref.nodeId && r?.field === ref.field));
+ if (keep.length === 0) delete def.value.valueSource;
+ else def.value.valueSource = keep;
+};
+
+// 清空全部引用
const clearValueSource = () => {
if (!def.value) return;
delete def.value.valueSource;
@@ -446,20 +477,26 @@ const variantLabel = (idx: number): string => templateLabel(def.value?.enumValue
.mf-ref-bound {
display: flex;
- align-items: center;
- gap: 8px;
+ align-items: flex-start;
+ gap: 6px;
font-size: 12px;
color: #3b82f6;
background: #eff6ff;
border-radius: 4px;
- padding: 4px 8px;
+ padding: 6px 8px;
- .mf-ref-bound-text {
+ .mf-ref-tags {
flex: 1;
min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+
+ .mf-ref-tag {
+ :deep(.el-tag__content) {
+ font-size: 12px;
+ }
+ }
}
&.is-invalid {
diff --git a/src/views/settings/workflow/component/ModelParamsForm.vue b/src/views/settings/workflow/component/ModelParamsForm.vue
index 9907b22..2b90608 100644
--- a/src/views/settings/workflow/component/ModelParamsForm.vue
+++ b/src/views/settings/workflow/component/ModelParamsForm.vue
@@ -14,6 +14,7 @@
:field-def="def"
:path="key"
:upstream-nodes="upstreamNodes"
+ :multi="multi"
/>
@@ -33,7 +34,7 @@ interface UpstreamNodeInfo {
outputFields: { field: string; label: string }[];
}
-const props = defineProps<{ modelRequestParams: Record | null; upstreamNodes?: UpstreamNodeInfo[] }>();
+const props = defineProps<{ modelRequestParams: Record | null; upstreamNodes?: UpstreamNodeInfo[]; multi?: boolean }>();
const emit = defineEmits<{ 'update:modelRequestParams': [Record | null] }>();
const localParams = ref>({});
diff --git a/src/views/settings/workflow/component/NodeConfigPanel.vue b/src/views/settings/workflow/component/NodeConfigPanel.vue
index c37e0f1..347e381 100644
--- a/src/views/settings/workflow/component/NodeConfigPanel.vue
+++ b/src/views/settings/workflow/component/NodeConfigPanel.vue
@@ -13,8 +13,39 @@
@update:model-value="updateNodeDesc"
/>
-
- {{ selectedNode.data?.nodeCode }}
+
+
+
+
+
+
+
+
+
+
+
@@ -219,6 +250,7 @@
@@ -245,6 +277,7 @@
@@ -291,6 +324,8 @@ interface NodeData {
negativePrompt?: string;
patchLayout?: boolean;
isSaveFile?: boolean;
+ preTool?: string | null;
+ postTool?: string | null;
runFormFields?: any[];
// 子流程节点(sub_flow):引入的工作流配置(workflowId + 引入参数)
subFlowConfig?: any;
@@ -298,6 +333,9 @@ interface NodeData {
interface NodeConfig {
formConfig: any[];
+ preToolOption: any[];
+ postToolOption: any[];
+ isMultiParameter: boolean;
modelConfigOption: boolean;
formConfigOption: boolean;
skillOption: boolean;
@@ -465,6 +503,42 @@ const updateModelRequestParams = (params: Record | null) => {
emit('update:selectedNode', updatedNode);
};
+// 前置/后置方法配置定义(节点库 preToolOption / postToolOption,仅 model 节点提供;取首个配置项渲染)
+const preToolDef = computed(() => {
+ const defs = props.nodeConfig?.preToolOption;
+ return Array.isArray(defs) && defs.length > 0 ? defs[0] : null;
+});
+const postToolDef = computed(() => {
+ const defs = props.nodeConfig?.postToolOption;
+ return Array.isArray(defs) && defs.length > 0 ? defs[0] : null;
+});
+
+// 前置方法值写节点顶层 preTool(与 outputConfig 平级)
+const updatePreTool = (value: string) => {
+ if (!props.selectedNode?.data) return;
+ const updatedNode = {
+ ...props.selectedNode,
+ data: {
+ ...props.selectedNode.data,
+ preTool: value,
+ },
+ };
+ emit('update:selectedNode', updatedNode);
+};
+
+// 后置方法值写节点顶层 postTool(与 outputConfig 平级)
+const updatePostTool = (value: string) => {
+ if (!props.selectedNode?.data) return;
+ const updatedNode = {
+ ...props.selectedNode,
+ data: {
+ ...props.selectedNode.data,
+ postTool: value,
+ },
+ };
+ emit('update:selectedNode', updatedNode);
+};
+
// 子流程引入参数就地变更(ModelField 已就地写共享引用):
// 仅刷新节点引用同步 VueFlow / 父组件状态;fields 为同一数组引用,避免与 SubFlowParams 深 watch 形成循环
const updateSubFlowFields = (fields: SubFlowField[]) => {
diff --git a/src/views/settings/workflow/component/SubFlowParams.vue b/src/views/settings/workflow/component/SubFlowParams.vue
index 1350bcd..7959d2d 100644
--- a/src/views/settings/workflow/component/SubFlowParams.vue
+++ b/src/views/settings/workflow/component/SubFlowParams.vue
@@ -10,6 +10,7 @@
:field-def="f"
:path="f.field"
:upstream-nodes="upstreamNodes"
+ :multi="multi"
/>
@@ -26,6 +27,7 @@ defineOptions({ name: 'SubFlowParams' });
interface Props {
fields: SubFlowField[];
upstreamNodes?: any[];
+ multi?: boolean;
}
interface Emits {
diff --git a/src/views/settings/workflow/component/modelParamUtils.ts b/src/views/settings/workflow/component/modelParamUtils.ts
index abebb6e..807c243 100644
--- a/src/views/settings/workflow/component/modelParamUtils.ts
+++ b/src/views/settings/workflow/component/modelParamUtils.ts
@@ -188,7 +188,7 @@ export interface ExposedField {
value?: any;
defaultValue?: any; // 默认值:首页初始化/回显用
fieldConstraint?: any; // 字段约束:上传格式/大小/数量、数字 min/max 等
- valueSource?: any; // 引用上游节点输出({ nodeId, field });有则首页只读展示
+ valueSource?: any; // 引用上游节点输出([{ nodeId, field }] 数组,支持多选);有则首页只读展示
multiple?: boolean; // 多文件上传标记
refNodeId?: string; // 预留:引用其他节点功能(后续启用)
}
@@ -232,7 +232,7 @@ export function collectExposedFields(params: any, prefix = ''): ExposedField[] {
value: def.value,
defaultValue: def.defaultValue,
fieldConstraint: def.fieldConstraint && typeof def.fieldConstraint === 'object' ? def.fieldConstraint : undefined,
- valueSource: def.valueSource && typeof def.valueSource === 'object' ? def.valueSource : undefined,
+ valueSource: Array.isArray(def.valueSource) ? def.valueSource : undefined,
multiple: def.multiple || def.fieldType === 'uploadMultiple' || undefined,
});
}
@@ -243,12 +243,14 @@ export function collectExposedFields(params: any, prefix = ''): ExposedField[] {
// ===== 模型请求参数扁平清单(modelRequestParamsPath)=====
// 后端契约:保存时与 modelRequestParams 平级新增 modelRequestParamsPath,
// 为嵌套模型参数树的全部叶子字段扁平清单(path 文法与 collectExposedFields 一致),
-// 后端按 path 重组请求参数。value 保留当前值;valueSource 规则:
-// - 已有引用配置(用户在设计器配置「引用上游」)→ 原样保留 { nodeId, field }
-// - 无引用但勾选「表单展示」(runtimeShow=true)→ 补 { nodeId: 开始节点id, field: path }
+// 后端按 path 重组请求参数。value 保留当前值;valueSource 规则(统一数组 [{ nodeId, field }]):
+// - 已有引用配置(用户在设计器配置「引用上游」)→ 原样保留数组
+// - 无引用但勾选「表单展示」(runtimeShow=true)→ 补 [{ nodeId: 开始节点id, field: path }]
// - 其余 → 不带 valueSource
export interface ModelRequestParamsPathItem {
path: string;
+ // 叶子字段中文名(回显/展示用;无 label 时兜底 path 最后一段字段名)
+ label: string;
type: string;
required: boolean;
value: any;
@@ -290,17 +292,44 @@ function collectAllLeafFields(params: any, prefix = ''): { path: string; def: an
// 生成 modelRequestParamsPath 扁平清单
// 所有叶子字段(含对象/数组内部)都支持「引用上游」,valueSource 原样保留;
// 与编辑器「引用上级输出与表单展示同级」语义一致
-export function buildModelRequestParamsPath(params: any, startNodeId = ''): ModelRequestParamsPathItem[] {
+export function buildModelRequestParamsPath(
+ params: any,
+ startNodeId = '',
+ // 节点输出索引(id → 节点名 + 字段 label),用于补全 valueSource 的引用来源 label
+ outputIndex?: Map }>
+): ModelRequestParamsPathItem[] {
const fields = collectAllLeafFields(params);
+ // 引用来源 label:节点名.字段label(无索引/未命中时兜底 field)
+ const resolveRefLabel = (nodeId: string, field: string): string => {
+ const info = outputIndex?.get(nodeId);
+ if (!info) return field;
+ const fieldLabel = info.fields.get(field) || field;
+ return info.name ? `${info.name}.${fieldLabel}` : fieldLabel;
+ };
return fields.map(({ path, def }) => {
- const valueSource =
- def.valueSource && typeof def.valueSource === 'object'
- ? def.valueSource // 引用上游:前端编辑态 { nodeId, field } 原样保留
- : def.runtimeShow === true && startNodeId
- ? { nodeId: startNodeId, field: path } // 勾选「表单展示」:补后端契约 { nodeId, field }
- : undefined;
+ let valueSource: any;
+ if (Array.isArray(def.valueSource)) {
+ // 引用上游:前端编辑态 [{ nodeId, field }] 数组,逐项补 label
+ valueSource = def.valueSource.map((r: any) =>
+ r && r.nodeId && r.field
+ ? { nodeId: r.nodeId, field: r.field, label: resolveRefLabel(r.nodeId, r.field) }
+ : r
+ );
+ } else if (def.valueSource && typeof def.valueSource === 'object') {
+ // 兼容旧 DSL 单对象 { nodeId, field }
+ const r = def.valueSource as any;
+ valueSource = [
+ r && r.nodeId && r.field
+ ? { nodeId: r.nodeId, field: r.field, label: resolveRefLabel(r.nodeId, r.field) }
+ : r,
+ ];
+ } else if (def.runtimeShow === true && startNodeId) {
+ // 勾选「表单展示」:补后端契约数组(指向开始节点,label 取开始节点字段名)
+ valueSource = [{ nodeId: startNodeId, field: path, label: resolveRefLabel(startNodeId, path) }];
+ }
return {
path,
+ label: def.label || path.split('.').pop() || '',
type: def.type || 'string',
required: Boolean(def.required),
value: def.value ?? '',
@@ -319,8 +348,10 @@ export function restoreRuntimeShow(params: any, fields: ExposedField[] | null |
const cur = resolvePath(params, f.path);
if (cur && typeof cur === 'object' && !Array.isArray(cur)) {
cur.runtimeShow = true;
- if (startNodeId && cur.valueSource && typeof cur.valueSource === 'object' && cur.valueSource.nodeId === startNodeId) {
- delete cur.valueSource;
+ if (startNodeId && Array.isArray(cur.valueSource)) {
+ // 移除指向开始节点(表单展示标记)的引用;全部移除后删除整个 valueSource
+ cur.valueSource = cur.valueSource.filter((r: any) => !(r && r.nodeId === startNodeId));
+ if (cur.valueSource.length === 0) delete cur.valueSource;
}
}
}
@@ -334,7 +365,7 @@ export function attachFormFieldValueSources(params: any, fields: ExposedField[]
if (!f || typeof f.path !== 'string') continue;
const cur = resolvePath(params, f.path);
if (cur && typeof cur === 'object' && !Array.isArray(cur)) {
- cur.valueSource = { nodeId: startNodeId, field: f.path };
+ cur.valueSource = [{ nodeId: startNodeId, field: f.path }];
}
}
}
diff --git a/src/views/settings/workflow/component/subFlowTypes.ts b/src/views/settings/workflow/component/subFlowTypes.ts
index 2a528c7..4dc4a23 100644
--- a/src/views/settings/workflow/component/subFlowTypes.ts
+++ b/src/views/settings/workflow/component/subFlowTypes.ts
@@ -19,7 +19,7 @@ export interface SubFlowField {
// 多文件上传标记
multiple?: boolean;
// 引用上级节点输出(主工作流上游);有值则编辑器内只读引用展示
- valueSource?: { nodeId: string; field: string } | null;
+ valueSource?: { nodeId: string; field: string }[] | null;
// 编辑器内「表单展示」勾选(ModelField 用 runtimeShow)
runtimeShow?: boolean;
}
@@ -45,7 +45,7 @@ export interface SubFlowDslField {
fieldConstraint?: any;
options?: any[] | null;
multiple?: boolean;
- valueSource?: { nodeId: string; field: string } | null;
+ valueSource?: { nodeId: string; field: string }[] | null;
// 勾选表单展示 → 聚合进主工作流开始节点(首页表单可填)
isFormField?: boolean;
}
diff --git a/src/views/settings/workflow/index.vue b/src/views/settings/workflow/index.vue
index d511ef0..239264d 100644
--- a/src/views/settings/workflow/index.vue
+++ b/src/views/settings/workflow/index.vue
@@ -179,6 +179,7 @@ interface NodeData {
patchLayout?: boolean;
isSaveFile?: boolean;
preTool?: string | null;
+ postTool?: string | null;
runFormFields?: RunFormField[]; // 仅开始节点使用
// 子流程节点(sub_flow):引入的工作流配置(workflowId + 引入参数)
subFlowConfig?: SubFlowConfig | null;
@@ -205,6 +206,9 @@ const nodeConfigMap = computed(() => {
string,
{
formConfig: any[];
+ preToolOption: any[];
+ postToolOption: any[];
+ isMultiParameter: boolean;
modelConfigOption: boolean;
formConfigOption: boolean;
skillOption: boolean;
@@ -217,6 +221,10 @@ const nodeConfigMap = computed(() => {
group.nodes.forEach((item) => {
map.set(item.key, {
formConfig: item.presetOption || [],
+ // 前置/后置方法配置定义(后端新增,仅 model 节点提供;值保存到节点顶层 preTool/postTool 字段)
+ preToolOption: item.preToolOption || [],
+ postToolOption: item.postToolOption || [],
+ isMultiParameter: item.isMultiParameter || false,
modelConfigOption: item.modelConfigOption || false,
formConfigOption: item.formConfigOption || false,
skillOption: item.skillOption || false,
@@ -656,7 +664,7 @@ const syncRunFormFields = () => {
value: f.value ?? f.defaultValue ?? '',
defaultValue: f.defaultValue,
fieldConstraint: f.fieldConstraint && typeof f.fieldConstraint === 'object' ? f.fieldConstraint : undefined,
- valueSource: f.valueSource && typeof f.valueSource === 'object' ? f.valueSource : undefined,
+ valueSource: Array.isArray(f.valueSource) ? f.valueSource : undefined,
options: Array.isArray(f.options) ? f.options : undefined,
multiple: isUploadMultiple || f.multiple || undefined,
nodeId: n.id,
@@ -1102,7 +1110,7 @@ const buildOutputConfig = (node: Node, startNodeId = '') => {
const entry = formConfig.find((f: any) => f.field === def.field);
const value = entry?.value ?? '';
// 勾选「表单展示」的字段:值来自开始节点表单,补 valueSource 标记(后端契约)
- const valueSource = def.isFormField === true ? { nodeId: startNodeId, field: def.field } : undefined;
+ const valueSource = def.isFormField === true ? [{ nodeId: startNodeId, field: def.field }] : undefined;
if (def.field === 'responseType') {
const expand = entry?.expand || [];
return {
@@ -1131,7 +1139,7 @@ const buildOutputConfig = (node: Node, startNodeId = '') => {
label: f.label || f.field || '',
value: f.value ?? '',
required: Boolean(f.required),
- ...(fieldName ? { valueSource: { nodeId: startNodeId, field: fieldName } } : {}),
+ ...(fieldName ? { valueSource: [{ nodeId: startNodeId, field: fieldName }] } : {}),
...(f.type === 'uploadMultiple'
? {
fieldConstraint: {
@@ -1169,7 +1177,7 @@ const buildOutputConfig = (node: Node, startNodeId = '') => {
label: def.label || def.field,
// isFormField 字段值在开始节点运行表单,不重复保存(回显从开始节点反查)
...(isForm ? {} : { value: entry?.value ?? '' }),
- ...(isForm ? { valueSource: { nodeId: startNodeId, field: def.field } } : {}),
+ ...(isForm ? { valueSource: [{ nodeId: startNodeId, field: def.field }] } : {}),
};
});
};
@@ -1203,9 +1211,9 @@ const serializeSubFlowConfig = (config: SubFlowConfig | null | undefined, node?:
// - 引用上级输出:值来自主工作流上游节点,统一转 field
// - 其余:无引用(静态默认值)
valueSource: f.runtimeShow
- ? { nodeId: startNodeId, field: f.field }
- : f.valueSource && typeof f.valueSource === 'object'
- ? { nodeId: f.valueSource.nodeId, field: f.valueSource.field }
+ ? [{ nodeId: startNodeId, field: f.field }]
+ : Array.isArray(f.valueSource)
+ ? f.valueSource.map((vs: any) => ({ nodeId: vs.nodeId, field: vs.field }))
: null,
isFormField: Boolean(f.runtimeShow),
})),
@@ -1232,11 +1240,12 @@ const buildSubFlowConfigFromDsl = (subConfig: any, startNodeId = '') => {
multiple: Boolean(f.multiple) || f.fieldType === 'uploadMultiple',
valueSource: (() => {
const vs = f.valueSource;
+ // 统一数组:兼容旧 DSL 单对象
+ const arr = Array.isArray(vs) ? vs : vs && typeof vs === 'object' ? [vs] : [];
// 指向开始节点:值为表单展示(首页可填),编辑器不显示为引用(勾选由 isFormField 恢复)
- if (vs && typeof vs === 'object' && vs.nodeId && vs.nodeId === startNodeId) return null;
+ const refs = arr.filter((r: any) => !(r && r.nodeId && r.nodeId === startNodeId));
// 引用上级:后端 field(兼容旧 DSL 的 fieldName)转回前端 field 结构
- if (vs && typeof vs === 'object') return { nodeId: vs.nodeId, field: vs.fieldName ?? vs.field };
- return null;
+ return refs.length ? refs.map((r: any) => ({ nodeId: r.nodeId, field: r.fieldName ?? r.field })) : null;
})(),
runtimeShow: Boolean(f.isFormField),
})),
@@ -1317,6 +1326,7 @@ const loadWorkflowFromDsl = (dsl: any) => {
patchLayout: n.patchLayout || false,
isSaveFile: Boolean(n.isSaveFile),
preTool: n.preTool ?? null,
+ postTool: n.postTool ?? null,
// 子流程节点:恢复引入的工作流配置(无 subConfig 时返回 null,兼容旧 DSL)
...(n.nodeCode === 'sub_flow' ? { subFlowConfig: buildSubFlowConfigFromDsl(n.subConfig, startNodeId) } : {}),
// 开始节点运行表单字段:新格式存 outputConfig;旧 DSL 顶层 runFormFields 兜底兼容
@@ -1403,6 +1413,16 @@ const confirmSaveWorkflow = async () => {
ElMessage.warning(`存在重名运行字段:${dupNames.join('、')},首页表单可能出现冲突`);
}
+ // 节点 id → 名称与输出字段(field → label)索引,供 modelRequestParamsPath 的 valueSource label 补全
+ const outputFieldsIndex = new Map }>();
+ nodes.value.forEach((n) => {
+ const fields = getNodeOutputFields(n);
+ outputFieldsIndex.set(n.id, {
+ name: n.data?.label || n.id,
+ fields: new Map(fields.map((f: any) => [f.field, f.label || f.field])),
+ });
+ });
+
const workflowDsl = {
version: '1.0.0',
startNodeId: startNode?.id || '',
@@ -1432,6 +1452,7 @@ const confirmSaveWorkflow = async () => {
height: gNode.dimensions?.height || gNode.height || 80,
},
...(n.data?.preTool ? { preTool: n.data.preTool } : {}),
+ ...(n.data?.postTool ? { postTool: n.data.postTool } : {}),
isSaveFile: Boolean(n.data?.isSaveFile),
// 子流程节点:序列化引入的工作流配置(含 workflowId 与 isFormField 标记);其余节点保持 null
subConfig: nodeCode === 'sub_flow' ? serializeSubFlowConfig(n.data?.subFlowConfig, n, startNode?.id || '') : null,
@@ -1444,7 +1465,7 @@ const confirmSaveWorkflow = async () => {
: {}),
modelRequestParams: savedModelRequestParams,
// 全部叶子字段扁平清单(path + 值 + 值来源),后端按 path 重组请求参数;基于编辑器原始嵌套树生成,保留用户配置的引用 valueSource
- modelRequestParamsPath: rawModelParams ? buildModelRequestParamsPath(rawModelParams, startNode?.id || '') : null,
+ modelRequestParamsPath: rawModelParams ? buildModelRequestParamsPath(rawModelParams, startNode?.id || '', outputFieldsIndex) : null,
// 勾选的「表单展示」字段(路径带实例索引),已在上方基于完整结构收集
...(savedModelFormFields && savedModelFormFields.length > 0 ? { modelFormFields: savedModelFormFields } : {}),
// 模型返回参数随工作流保存,保证重开后仍可被下游引用