diff --git a/package.json b/package.json index 7ea3721..5fbded2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shrimp-server", - "version": "0.6.10", + "version": "0.6.11", "description": "Shrimp 框架后端 — 声明式字段驱动的 Node.js 管理后台框架(Express + Mongoose + JWT)", "main": "dist/main/index.js", "types": "dist/main/index.d.ts", diff --git a/src/IShrimpType.ts b/src/IShrimpType.ts index af6903d..bd02d7d 100644 --- a/src/IShrimpType.ts +++ b/src/IShrimpType.ts @@ -237,6 +237,8 @@ export const ShrimpFieldTypes = { Relation: "relation" as ShrimpFieldType, Json: "json" as ShrimpFieldType, Tab: "tab" as ShrimpFieldType, + Object: "object" as ShrimpFieldType, + Array: "array" as ShrimpFieldType, } /** 字段可编辑状态 */ @@ -276,6 +278,10 @@ export interface IFieldConfig { rules?: IFieldRule[] // 通用验证规则(前后端共用) /** tab 类型专用:每个 type 对应的子字段配置 */ tabs?: { key: any; label: ILabel; fields: IFieldConfig[] }[] + /** object 类型专用:嵌套字段组 */ + fields?: IFieldConfig[] + /** array 类型专用:每个元素的字段配置(递归 IFieldConfig;元素无需 key,label 用作每项标题) */ + item?: IFieldConfig /** tab 类型专用:import 反序列化(把字符串还原成对象,如 xlsx 导入),默认 JSON.parse */ parse?: (text: string) => any } diff --git a/src/Shrimp/ShrimpTableModule.ts b/src/Shrimp/ShrimpTableModule.ts index 1779000..2a8c5e9 100644 --- a/src/Shrimp/ShrimpTableModule.ts +++ b/src/Shrimp/ShrimpTableModule.ts @@ -246,7 +246,7 @@ export class ShrimpTableModule = ITableCrudProto b.ids.add(String(id)) } for (const row of rows) { - this.collectRefs(this.fields, (k) => row[k], add) + for (const f of this.fields) this.collectOne(f, row[f.key], add) } if (buckets.size === 0) return {} const refs: Record> = {} @@ -263,16 +263,18 @@ export class ShrimpTableModule = ITableCrudProto } /** 递归收集 relation 引用:relation 叶子直接收;tab 按 value.type 进选中分支继续下钻(覆盖嵌套/tab套tab) */ - private collectRefs(fields: IFieldConfig[], valueOf: (key: string) => any, add: (moduleKey: string, labelKey: string, id: any) => void): void { - for (const f of fields) { - const v = valueOf(f.key) - if (v == null) continue - if (f.type === "relation" && f.relation) { - ;(Array.isArray(v) ? v : [v]).forEach((id: any) => id != null && add(f.relation!.moduleKey, f.relation!.labelKey, id)) - } else if (f.type === ShrimpFieldTypes.Tab && Array.isArray(f.tabs) && typeof v === "object") { - const branch = f.tabs.find(t => t.key === v.type) - if (branch) this.collectRefs(branch.fields, (k) => v[k], add) - } + /** 单个 (字段,值) 收集 relation 引用,递归复合(tab/object/array)。array 的 item 无 key —— 元素即值 */ + private collectOne(f: IFieldConfig, v: any, add: (moduleKey: string, labelKey: string, id: any) => void): void { + if (v == null) return + if (f.type === "relation" && f.relation) { + ;(Array.isArray(v) ? v : [v]).forEach((id: any) => id != null && add(f.relation!.moduleKey, f.relation!.labelKey, id)) + } else if (f.type === ShrimpFieldTypes.Tab && Array.isArray(f.tabs) && typeof v === "object") { + const branch = f.tabs.find(t => t.key === v.type) + if (branch) for (const c of branch.fields) this.collectOne(c, v[c.key], add) + } else if (f.type === ShrimpFieldTypes.Object && Array.isArray(f.fields) && typeof v === "object") { + for (const c of f.fields) this.collectOne(c, v[c.key], add) + } else if (f.type === ShrimpFieldTypes.Array && f.item && Array.isArray(v)) { + for (const el of v) this.collectOne(f.item, el, add) } }