首页调试工作流管理
This commit is contained in:
@@ -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 时代 formConfig(http 仅 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.value;form → 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;
|
||||
}
|
||||
Reference in New Issue
Block a user