首页调试工作流管理

This commit is contained in:
2026-08-13 18:02:35 +08:00
parent 2ae59c9252
commit 8e7948cff5
8 changed files with 967 additions and 135 deletions
+239
View File
@@ -0,0 +1,239 @@
// ===== 首页执行工作流管理 DSL 的解析与写回工具 =====
// 独立实现,不依赖 settings/workflow 或 settings/creation 的旧结构。
// 字段路径文法与工作流管理 modelParamUtils 一致:key / .attrs. / .value[i]
export interface HomeFormField {
path: string;
label: string;
type: string;
fieldType: string;
required: boolean;
value?: any;
default?: any;
options?: any[];
fieldConstraint?: any;
// 旧兼容:creation 时代 http body 中 showInForm 的子字段
__isHttpBodyChild?: boolean;
bodyKey?: string;
}
export function deepClone<T>(val: T): T {
return JSON.parse(JSON.stringify(val ?? null)) as T;
}
// 按路径走回对象;解析失败(schema 变更导致字段缺失)返回 undefined
export function resolvePath(params: any, path: string): any {
if (!path) return undefined;
const segments = path.split('.');
let cur = params;
for (const seg of segments) {
if (cur === undefined || cur === null || typeof cur !== 'object' || Array.isArray(cur)) return undefined;
if (seg === 'attrs') {
cur = cur.attrs;
if (!cur || typeof cur !== 'object') return undefined;
} else {
const m = seg.match(/^value\[(\d+)\]$/);
if (m) {
const idx = Number(m[1]);
if (!Array.isArray(cur.value) || idx >= cur.value.length) return undefined;
cur = cur.value[idx];
} else {
cur = cur[seg];
}
}
}
return cur;
}
// 字段类型映射:modelRequestParams def → MainContent 控件 type
function mapFieldType(def: any): string {
const ft = def.fieldType || '';
const t = def.type || 'string';
if (ft === 'number' || t === 'number') return 'number';
if (ft === 'boolean' || t === 'boolean' || ft === 'switch') return 'switch';
if (ft === 'select') return 'select';
if (ft === 'textarea') return 'textarea';
if (ft === 'upload' || ft === 'file' || ft === 'fileUpload') {
return ft === 'uploadMultiple' || def.multiple ? 'uploadMultiple' : 'upload';
}
return 'input';
}
// 选项归一化:兼容 {label,value} / {key,value} / value.options 嵌套三种来源
function normalizeOptions(def: any): any[] | undefined {
let raw: any[] | undefined;
if (Array.isArray(def.options) && def.options.length > 0) raw = def.options;
else if (def.value && typeof def.value === 'object' && !Array.isArray(def.value) && Array.isArray(def.value.options))
raw = def.value.options;
else if (Array.isArray(def.enumValues) && def.enumValues.length > 0) raw = def.enumValues;
if (!raw) return undefined;
return raw.map((o: any) => {
if (o && typeof o === 'object' && !Array.isArray(o)) {
const val = o.value !== undefined ? o.value : o.key;
const label = o.label !== undefined ? o.label : o.key;
return { label: label === undefined ? String(val) : label, value: val };
}
return { label: String(o), value: o };
});
}
// select 字段当前值:兼容 def.value 为 { value, options } 对象的特殊结构
function leafValue(def: any): any {
const v = def?.value;
if (v && typeof v === 'object' && !Array.isArray(v) && 'value' in v) return v.value;
return v;
}
// model 节点:遍历 modelRequestParams 收集 runtimeShow === true 的叶子字段
function collectModelFields(node: any): HomeFormField[] {
const params = node?.modelConfig?.modelRequestParams;
if (!params || typeof params !== 'object' || Array.isArray(params)) return [];
const fields: HomeFormField[] = [];
const walk = (def: any, prefix: string) => {
for (const key of Object.keys(def)) {
const f = def[key];
if (!f || typeof f !== 'object') continue;
const path = prefix ? `${prefix}.${key}` : key;
const t = f.type;
if (t === 'object') {
if (f.attrs && typeof f.attrs === 'object' && !Array.isArray(f.attrs)) walk(f.attrs, `${path}.attrs`);
} else if (t === 'array') {
// 仅遍历已有实例;元素为 { type:'object', attrs } 包装时才递归
if (Array.isArray(f.value)) {
f.value.forEach((item: any, i: number) => {
if (item && typeof item === 'object' && item.attrs && typeof item.attrs === 'object' && !Array.isArray(item.attrs)) {
walk(item.attrs, `${path}.value[${i}].attrs`);
}
});
}
} else if (f.runtimeShow === true) {
fields.push({
path,
label: f.label || key,
type: mapFieldType(f),
fieldType: f.fieldType || 'string',
required: !!f.required,
value: leafValue(f),
default: f.defaultValue,
options: normalizeOptions(f),
fieldConstraint: f.fieldConstraint && typeof f.fieldConstraint === 'object' ? f.fieldConstraint : undefined,
});
}
}
};
walk(params, '');
return fields;
}
// form 节点:outputConfig 为用户自定义运行字段
function collectFormNodeFields(node: any): HomeFormField[] {
const out = Array.isArray(node?.outputConfig) ? node.outputConfig : [];
return out
.filter((o: any) => o && typeof o === 'object' && o.field !== undefined)
.map((o: any) => ({
path: o.field,
label: o.label || o.field,
type: o.type || 'input',
fieldType: o.type || 'string',
required: Boolean(o.required),
value: o.value,
default: o.value,
options: Array.isArray(o.options) && o.options.length > 0 ? o.options : undefined,
fieldConstraint: o.fieldConstraint && typeof o.fieldConstraint === 'object' ? o.fieldConstraint : undefined,
}));
}
// 旧兼容:creation 时代 formConfighttp 仅 body 中 showInForm 的子字段)
function collectLegacyFormConfig(node: any): HomeFormField[] {
const fields = Array.isArray(node?.formConfig) ? node.formConfig : [];
const result: HomeFormField[] = [];
fields.forEach((field: any) => {
if (!field) return;
if (field.expand && typeof field.expand === 'object' && field.expand.editable === false) return;
if (String(node?.nodeCode || '').toLowerCase() === 'http') {
if (field.field !== 'body') return;
const bodyVal = field.value;
if (!bodyVal || typeof bodyVal !== 'object' || Array.isArray(bodyVal)) return;
Object.entries(bodyVal).forEach(([bodyKey, bodyItem]: [string, any]) => {
if (!bodyItem || bodyItem.showInForm !== true) return;
result.push({
__isHttpBodyChild: true,
bodyKey,
path: `body.${bodyKey}`,
label: bodyItem.key || bodyKey,
type: bodyItem.fieldType || 'input',
fieldType: bodyItem.fieldType || 'string',
required: false,
value: bodyItem.value,
default: bodyItem.value,
fieldConstraint: bodyItem.fieldConstraint && typeof bodyItem.fieldConstraint === 'object' ? bodyItem.fieldConstraint : undefined,
});
});
return;
}
result.push({
path: field.field || field.label,
label: field.label || field.field,
type: field.type || 'input',
fieldType: field.fieldType || field.type || 'string',
required: Boolean(field.required),
value: field.value,
default: field.default,
options: Array.isArray(field.options) ? field.options : undefined,
fieldConstraint: field.fieldConstraint && typeof field.fieldConstraint === 'object' ? field.fieldConstraint : undefined,
});
});
return result;
}
// 主入口:DSL 节点 → 首页扁平表单字段
// model → modelRequestParams runtimeShow 叶子;form → outputConfig;其余 → 旧 formConfig 兜底
export function collectHomeFormFields(node: any): HomeFormField[] {
const code = String(node?.nodeCode || '').toLowerCase();
let dslFields: HomeFormField[] = [];
if (code === 'model') dslFields = collectModelFields(node);
else if (code === 'form') dslFields = collectFormNodeFields(node);
// http 等其它节点:新 DSL 无运行时字段(outputConfig 为请求配置),交给旧 formConfig 兜底
if (dslFields.length > 0) return dslFields;
return collectLegacyFormConfig(node);
}
// 首页表单值写回 DSL 对应字段(model → modelRequestParams.path.valueform → outputConfig[].value
export function applyHomeFormValues(nodes: any[], formValues: Record<string, any>): void {
if (!Array.isArray(nodes)) return;
for (const node of nodes) {
const code = String(node?.nodeCode || '').toLowerCase();
for (const f of collectHomeFormFields(node)) {
const key = `${node.id || node.nodeCode}|${f.path}`;
const val = formValues[key];
// 仅跳过未初始化的 key;null(如数字清空)也要写回
if (val === undefined) continue;
if (code === 'model') {
const target = resolvePath(node?.modelConfig?.modelRequestParams, f.path);
if (target && typeof target === 'object' && !Array.isArray(target)) target.value = val;
} else if (code === 'form') {
const item = (node?.outputConfig || []).find((o: any) => o && o.field === f.path);
if (item) item.value = val;
} else if (f.__isHttpBodyChild && f.bodyKey && Array.isArray(node?.formConfig)) {
const bodyField = node.formConfig.find((x: any) => x && x.field === 'body');
if (bodyField?.value && typeof bodyField.value === 'object' && !Array.isArray(bodyField.value) && bodyField.value[f.bodyKey]) {
bodyField.value[f.bodyKey].value = val;
}
}
}
}
}
// 模板完整性校验:返回缺模型等缺失项,供补全弹窗提示
export function checkTemplateMissing(flowContent: any): { nodeId: string; nodeName: string; reason: string }[] {
const nodes = Array.isArray(flowContent?.nodes) ? flowContent.nodes : [];
const missing: { nodeId: string; nodeName: string; reason: string }[] = [];
for (const n of nodes) {
if (String(n?.nodeCode || '').toLowerCase() === 'model' && !n?.modelConfig?.modelId) {
missing.push({ nodeId: n?.id || '', nodeName: n?.name || n?.nodeCode || '模型节点', reason: '未选择模型' });
}
}
return missing;
}