模型参数约束透传:collectExposedFields 从 constraint 归一化 fieldConstraint 供首页表单校验

模板字段约束实际挂在 constraint 键(min/max/uploadRules 等),原读取 fieldConstraint 键导致模型勾选字段的约束丢失。新增 toFieldConstraint 归一化(min→minValue、max→maxValue、uploadRules/accept→fileTypes/maxFileSize/maxFileCount),并将格式分隔符统一为英文逗号,修复 wan 模型音频上传被误拦。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-20 11:46:50 +08:00
co-authored by Claude
parent 20bab0997e
commit 63ac39cd4a
@@ -193,6 +193,35 @@ export interface ExposedField {
refNodeId?: string; // 预留:引用其他节点功能(后续启用)
}
// 将模型模板字段级约束(constraintJsonEditor 风格:min/max/uploadRules/accept/maxSize/maxCount 等)
// 归一化为首页运行表单消费的 fieldConstraintminValue/maxValue/fileTypes/maxFileSize/maxFileCount)。
// 模板字段约束实际挂在 constraint 键下(如 video_url.url 的 uploadRules、duration 的 min/numberType),
// 与 collectExposedFields 原读取的 fieldConstraint 键不一致,需在此转换后随 modelFormFields 透传首页。
function toFieldConstraint(constraint: any): any {
if (!constraint || typeof constraint !== 'object') return undefined;
const out: Record<string, any> = {};
// 数字 min/max → 首页 el-input-number 的 minValue/maxValue
if (constraint.min !== undefined && constraint.min !== null) out.minValue = constraint.min;
if (constraint.max !== undefined && constraint.max !== null) out.maxValue = constraint.max;
// 上传约束:uploadRules 数组取第一条;兼容顶层 accept/maxSize/maxCount 单值形式
const rules = Array.isArray(constraint.uploadRules) ? constraint.uploadRules.filter((r: any) => r && typeof r === 'object') : [];
const rule = rules[0] || null;
const fileTypes = rule?.format ?? constraint.accept;
if (fileTypes !== undefined && fileTypes !== null && fileTypes !== '') {
// 模板 format 可能用中文顿号/空白分隔(如 wan 模型 "wav、mp3"),
// 统一转英文逗号,保证首页 split(',') 与扩展名匹配正确
out.fileTypes = String(fileTypes)
.replace(/[、\s]+/g, ',')
.replace(/,+/g, ',')
.replace(/^,|,$/g, '');
}
const maxSize = rule?.maxSize ?? constraint.maxSize;
if (maxSize !== undefined && maxSize !== null) out.maxFileSize = maxSize;
const maxCount = rule?.maxCount ?? constraint.maxCount;
if (maxCount !== undefined && maxCount !== null) out.maxFileCount = maxCount;
return Object.keys(out).length > 0 ? out : undefined;
}
// 收集 runtimeShow === true 的叶子字段,生成暴露清单
// 路径文法(与 restoreRuntimeShow 共用):
// object 子字段 → 父路径 + ".attrs." + 子key
@@ -234,7 +263,10 @@ export function collectExposedFields(params: any, prefix = ''): ExposedField[] {
options: Array.isArray(def.options) ? def.options : undefined,
value: def.value,
defaultValue: def.defaultValue,
fieldConstraint: def.fieldConstraint && typeof def.fieldConstraint === 'object' ? def.fieldConstraint : undefined,
// 优先透传已有的 fieldConstraint(首页格式);模板字段约束在 constraint 键时归一化补全
fieldConstraint:
(def.fieldConstraint && typeof def.fieldConstraint === 'object' ? def.fieldConstraint : undefined) ??
toFieldConstraint(def.constraint),
valueSource: (() => {
const vs = def.valueSource;
// 契约统一数组;兼容旧 DSL 单对象 { nodeId, field }