diff --git a/package.json b/package.json index 5b17b99..013f4ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shrimp-server", - "version": "0.7.5", + "version": "0.7.6", "description": "Shrimp 框架后端 — 声明式字段驱动的 Node.js 管理后台框架(Express + Mongoose + JWT)", "main": "dist/main/index.js", "types": "dist/main/index.d.ts", diff --git a/src/Shrimp/ShrimpTableModule.ts b/src/Shrimp/ShrimpTableModule.ts index 0f53dc9..4b8703e 100644 --- a/src/Shrimp/ShrimpTableModule.ts +++ b/src/Shrimp/ShrimpTableModule.ts @@ -68,9 +68,27 @@ export class ShrimpTableModule = ITableCrudProto return ShrimpTableModule.typeHandlers.get(field.type) } + /** 按类型递归判定"空":tab=没选type / object=所有子字段都空 / array=无元素或所有元素都空(多维递归)/ 标量=null|'' / 空数组 */ + private isEmptyValue(field: IFieldConfig, value: any): boolean { + if (value === undefined || value === null) return true + switch (field.type) { + case ShrimpFieldTypes.Tab: + return typeof value !== "object" || value.type == null + case ShrimpFieldTypes.Object: + if (typeof value !== "object" || !Array.isArray(field.fields)) return true + return field.fields.every(f => this.isEmptyValue(f, value[f.key])) + case ShrimpFieldTypes.Array: + if (!Array.isArray(value) || value.length === 0) return true + return field.item ? value.every(el => this.isEmptyValue(field.item!, el)) : false + default: + // 标量/relation/select:'' 或空数组(multiple)算空;0、false 不算空 + return value === "" || (Array.isArray(value) && value.length === 0) + } + } + /** 前置验证 */ private async validateField(field: IFieldConfig, value: any, item: Record): Promise { - const isEmpty = value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0) + const isEmpty = this.isEmptyValue(field, value) if (field.required && isEmpty) { return field.rules?.find(r => r.required)?.message || { t: 'shrimp.fieldRequired', params: { field: field.label } } @@ -196,6 +214,8 @@ export class ShrimpTableModule = ITableCrudProto // 出错只发字段名(label) + i18n key 的 message,不把开发用的 path 拼进可见文案。 // path 仍递归往下传(供将来 inline 标红按结构化方式携带,不在这里塞进 message)。 if (err) throw new ShrimpError(err) + // 空的复合(走到这必是非必填)→ 不下钻校验子:整个结构缺席,里面的 required 不该触发 + if (this.isEmptyValue(field, value)) return for (const child of this.childrenOf(field, value)) { await this.validateNode(child.field, child.value, [...path, child.key], rootItem) }