diff --git a/src/components/json-schema-editor/FieldConfigDialog.vue b/src/components/json-schema-editor/FieldConfigDialog.vue
index 3b81a66..982c0b3 100644
--- a/src/components/json-schema-editor/FieldConfigDialog.vue
+++ b/src/components/json-schema-editor/FieldConfigDialog.vue
@@ -65,7 +65,9 @@
+
import { computed, watch } from 'vue';
-import { FolderOpened, List, QuestionFilled, Link } from '@element-plus/icons-vue';
+import { FolderOpened, List, QuestionFilled } from '@element-plus/icons-vue';
+import MfReferenceButton from './MfReferenceButton.vue';
defineOptions({ name: 'ModelField' });
@@ -169,7 +132,7 @@ interface UpstreamNodeInfo {
id: string;
label: string;
nodeCode: string;
- outputFields: { field: string; label: string }[];
+ outputFields: { field: string; label: string; fieldType?: 'scalar' | 'object' | 'array' }[];
}
const props = defineProps<{ fieldDef: any; path?: string; upstreamNodes?: UpstreamNodeInfo[] }>();
@@ -203,9 +166,7 @@ const isTextarea = computed(() => isLeaf.value && fieldType.value === 'textarea'
const isUpload = computed(() => isLeaf.value && fieldType.value === 'upload');
// ===== 引用上级节点输出 =====
-// 可引用:叶子 + 非开关 + 非固定选项下拉(开关/下拉值来自模型;上传类型同样支持引用上级输出)
-const canValueSource = computed(() => isLeaf.value && !isSwitch.value && !isSelect.value);
-const hasUpstream = computed(() => (props.upstreamNodes?.length ?? 0) > 0);
+// 引用按钮与候选面板在 MfReferenceButton 中统一渲染(叶子与对象/数组整体均可引用)
const hasValueSource = computed(() => !!def.value?.valueSource);
// 已引用时的展示文案:{节点label}.{字段label}
const valueSourceLabel = computed(() => {
@@ -237,12 +198,6 @@ watch(
{ immediate: true }
);
-// 选中前驱节点输出字段:就地写 def.valueSource,复用深 watch 上传链路
-const setValueSource = (node: UpstreamNodeInfo, of: { field: string; label: string }) => {
- if (!def.value) return;
- def.value.valueSource = { nodeId: node.id, field: of.field };
-};
-
const clearValueSource = () => {
if (!def.value) return;
delete def.value.valueSource;
diff --git a/src/views/settings/workflow/component/modelParamUtils.ts b/src/views/settings/workflow/component/modelParamUtils.ts
index cf5df60..abebb6e 100644
--- a/src/views/settings/workflow/component/modelParamUtils.ts
+++ b/src/views/settings/workflow/component/modelParamUtils.ts
@@ -288,6 +288,8 @@ function collectAllLeafFields(params: any, prefix = ''): { path: string; def: an
}
// 生成 modelRequestParamsPath 扁平清单
+// 所有叶子字段(含对象/数组内部)都支持「引用上游」,valueSource 原样保留;
+// 与编辑器「引用上级输出与表单展示同级」语义一致
export function buildModelRequestParamsPath(params: any, startNodeId = ''): ModelRequestParamsPathItem[] {
const fields = collectAllLeafFields(params);
return fields.map(({ path, def }) => {
diff --git a/src/views/settings/workflow/index.vue b/src/views/settings/workflow/index.vue
index 6c70bae..d511ef0 100644
--- a/src/views/settings/workflow/index.vue
+++ b/src/views/settings/workflow/index.vue
@@ -149,6 +149,8 @@ interface RunFormField extends Omit {
interface UpstreamField {
field: string;
label: string;
+ // 输出字段类型:http 节点容器会额外产出 'object'/'array' 整体候选,标量或未标记视为叶子
+ fieldType?: 'scalar' | 'object' | 'array';
}
interface UpstreamNodeInfo {
id: string;
@@ -729,50 +731,61 @@ const parseSchema = (value: any): any => {
return null;
};
-// 递归收集 http 节点 response schema 的叶子输出字段(识别 JsonEditor 的 { type, attrs/value, label } 包裹结构):
+// 递归收集 http 节点 response schema 的输出字段(识别 JsonEditor 的 { type, attrs/value, label } 包裹结构):
+// 产出叶子字段 + 对象/数组容器整体候选(下游可引用整个结构);
// field 用实际返回数据路径(不含 attrs 元数据,如 data.url 而非 data.attrs.url.value),
// label 优先用字段配置的中文名(label),无则兜底路径 key
-const collectHttpLeafFields = (node: any, prefix = ''): UpstreamField[] => {
+const collectHttpOutputFields = (node: any, prefix = '', isRoot = true, suppressContainer = false): UpstreamField[] => {
+ const out: UpstreamField[] = [];
// JsonEditor 包裹结构:{ type: 'object'|'array'|'string'|..., attrs/value, label }
if (node && typeof node === 'object' && !Array.isArray(node) && typeof node.type === 'string') {
const jtype = node.type;
if (['object', 'array', 'string', 'number', 'boolean', 'null'].includes(jtype)) {
+ // 对象/数组容器:非根且非数组样本时,产出「整体」候选(引用整体 = 拿整个结构);标量不产整体
+ if (!isRoot && !suppressContainer && prefix && (jtype === 'object' || jtype === 'array')) {
+ out.push({ field: prefix, label: (typeof node.label === 'string' && node.label) || prefix, fieldType: jtype });
+ }
if (jtype === 'object') {
// 对象容器:子字段在 attrs 里,field 路径直接拼子字段名,不拼 attrs
const attrs = node.attrs;
if (attrs && typeof attrs === 'object' && !Array.isArray(attrs)) {
- const out: UpstreamField[] = [];
for (const k of Object.keys(attrs)) {
- out.push(...collectHttpLeafFields(attrs[k], prefix ? `${prefix}.${k}` : k));
+ out.push(...collectHttpOutputFields(attrs[k], prefix ? `${prefix}.${k}` : k, false, false));
}
- return out;
}
- return [];
+ return out;
}
if (jtype === 'array') {
- // 数组:取首元素样本作为结构(实际值为数组整体,field 不带索引)
+ // 数组:取首元素样本作为结构(实际值为数组整体,field 不带索引);
+ // 首元素为标量时不产叶子(数组整体已代表该元素),元素为对象时不产对象整体避免与数组整体 field 重复
const arr = node.attrs;
- if (Array.isArray(arr) && arr.length > 0) return collectHttpLeafFields(arr[0], prefix);
- return [];
+ if (Array.isArray(arr) && arr.length > 0) {
+ const el = arr[0];
+ if (el && typeof el === 'object' && !Array.isArray(el) && typeof el.type === 'string' && ['string', 'number', 'boolean', 'null'].includes(el.type)) {
+ return out;
+ }
+ return out.concat(collectHttpOutputFields(el, prefix, false, true));
+ }
+ return out;
}
// 标量叶子:label 为字段配置的中文名,无则兜底路径
- return [{ field: prefix, label: (typeof node.label === 'string' && node.label) || prefix }];
+ out.push({ field: prefix, label: (typeof node.label === 'string' && node.label) || prefix, fieldType: 'scalar' });
+ return out;
}
}
// 普通对象(根/未包裹):递归子字段;子字段为原始标量时以路径兜底产出
if (node && typeof node === 'object' && !Array.isArray(node)) {
- const out: UpstreamField[] = [];
for (const k of Object.keys(node)) {
const child = node[k];
const path = prefix ? `${prefix}.${k}` : k;
- if (child && typeof child === 'object') out.push(...collectHttpLeafFields(child, path));
- else out.push({ field: path, label: path });
+ if (child && typeof child === 'object') out.push(...collectHttpOutputFields(child, path, false, false));
+ else out.push({ field: path, label: path, fieldType: 'scalar' });
}
return out;
}
// 数组根:取首元素结构样本
- if (Array.isArray(node)) return node.length > 0 ? collectHttpLeafFields(node[0], prefix) : [];
- return [];
+ if (Array.isArray(node)) return node.length > 0 ? collectHttpOutputFields(node[0], prefix, isRoot, suppressContainer) : [];
+ return out;
};
// 取节点在设计时的可引用输出字段(运行时这些字段会产出实际值)
@@ -786,7 +799,7 @@ const getNodeOutputFields = (node: Node): UpstreamField[]
}));
}
if (nodeCode === 'http') {
- // http 节点:输出 = 结果返回结构(response schema)的所有叶子字段。
+ // http 节点:输出 = 结果返回结构(response schema)的叶子字段 + 对象/数组容器整体。
// 结果返回方式为主动拉取(responseType === 'pull')时,取主动拉取分支下配置的结果返回结构。
const formConfig = node.data?.formConfig || [];
const responseTypeEntry = formConfig.find((f: any) => f.field === 'responseType');
@@ -795,7 +808,7 @@ const getNodeOutputFields = (node: Node): UpstreamField[]
? (responseTypeEntry?.expand || []).find((e: any) => e.field === 'response')
: formConfig.find((f: any) => f.field === 'response');
const schema = parseSchema(responseField?.value);
- if (schema) return collectHttpLeafFields(schema);
+ if (schema) return collectHttpOutputFields(schema);
return [];
}
if (nodeCode === 'model') {