From 06aec7f0dc47f48f0baef2e59cd2e2280c0e851c Mon Sep 17 00:00:00 2001 From: wc24 Date: Tue, 30 Jun 2026 16:52:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(required):=20=E6=8C=89=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E9=80=92=E5=BD=92=E5=88=A4=E5=AE=9A=E7=A9=BA(tab/object/array?= =?UTF-8?q?=20=E5=90=84=E8=87=AA=E8=AF=AD=E4=B9=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isEmptyValue(field,value) 递归: tab=没选type / object=所有子字段都空 / array=无元素或 所有元素都空(多维递归) / 标量=null|''/空数组(0、false 不算空)。required=!isEmpty 自动 对所有类型生效。validateNode: 空的复合(非必填)不下钻校验子(整个结构缺席)。 --- package.json | 2 +- src/Shrimp/ShrimpTableModule.ts | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) 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) }