From 5ecbf2cc01f791470b7e886977c8268b12083150 Mon Sep 17 00:00:00 2001 From: wc24 Date: Wed, 10 Jun 2026 15:47:57 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20array/object=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=AE=BE=E8=AE=A1=E8=8D=89=E6=A1=88=EF=BC=88?= =?UTF-8?q?=E6=90=81=E7=BD=AE=E5=BE=85=E9=9C=80=E6=B1=82=EF=BC=8C=E5=90=AB?= =?UTF-8?q?=E5=89=8D=E5=90=8E=E7=AB=AF=E6=96=B9=E6=A1=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/array-object-field-design.md | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/array-object-field-design.md diff --git a/docs/array-object-field-design.md b/docs/array-object-field-design.md new file mode 100644 index 0000000..6a4d882 --- /dev/null +++ b/docs/array-object-field-design.md @@ -0,0 +1,74 @@ +# Array / Object 字段类型 —— 设计草案(暂未实现) + +> 状态:**搁置待需求**。当前没有字段需要它(powers 已用自定义类型 `powers` 解决,dropList 用自定义 type + Mixed 解决)。 +> 等出现「既要结构化数组/对象、又要框架级递归校验 + 自动表单渲染」的真实字段时,再按本文档实现。 + +## 背景:为什么有这份文档 + +### 触发点:powers 字段类型错误 +角色权限字段 `powers` 实际存的是权限 key 数组(`string[]`),但 `roleFields` 里错标成 `type: "string"`。在「字段前置验证」(`ShrimpTableModule.validateField`)加上类型校验后,String 分支 `typeof value !== 'string'` 把数组判为类型错误。 + +### 已采用的解法(v0.6.2,已上线) +把 powers 改成自定义类型 `type: "powers"`。原理:`validateField` 的类型校验是 switch-case,只认 string/number/boolean/date 四个内置类型,**任何自定义 type 不匹配任何 case → 自动跳过类型校验**;且未注册 typeHandler → 无 reqFilter/resFilter → 数组原样存取,不破坏前端权限树。required/rules 仍生效。 + +这是最小解,powers 问题已闭环。**本文档的 array/object 不是为 powers——powers 不需要它**。 + +## 何时才该实现本设计 + +满足以下条件之一再做,否则属于过度设计: +- 出现一个字段,值是**结构化数组**(如 `[{itemId,count,weight}]`),且希望**框架级递归校验** + **自动按子字段渲染表单**(而非像 dropList 那样手写专属前端组件 + 跳过校验)。 +- 多个字段重复出现「对象数组」形态,自定义类型方案的手写成本变得不划算。 + +判别:需求会把"要不要去重 / 要不要嵌套 / 摘要怎么显示"这些问题自然定下来,比现在凭空猜更准。 + +## 设计要点(实现时参考) + +### 与 multiple 的关系(关键,A 方案) +`multiple` 与 `array` 是**两个正交维度,不合并**: +- `multiple`:**选择基数修饰符**,依附 select/relation,含义"从固定候选集里选几个",**集合语义(不可重复)**。保持现状不动。 +- `array`:**值形态类型**,开放列表,元素类型由 `item` 定,可嵌套。 +- 判别标准:值来自**固定选项集**→ select/relation + multiple;值是**开放列表**→ array。 + +### 后端 shrimp-server + +**1. `IShrimpType.ts`** +```ts +// ShrimpFieldTypes 增加 +Array: "array", Object: "object" +// IFieldConfig 增加(复用 IFieldConfig 自身递归 —— tab 的 tabs[].fields 已有先例) +item?: IFieldConfig // array 元素的类型定义 +fields?: IFieldConfig[] // object 的子字段定义 +unique?: boolean // array 是否去重(集合语义,默认 false) +``` + +**2. `ShrimpTableModule.validateField` 递归校验** +- `array` 分支:值必须是数组 → 对每个元素递归 `validateField(field.item, 元素, item)`;`unique:true` 查重报错;min/max 已支持数组长度。 +- `object` 分支:值必须是对象 → 对 `field.fields` 每个子字段递归 `validateField(子字段, value[子key], value)`。 +- **multiple 特殊处理**:`multiple:true` 字段值是数组,但元素校验走字段自己的 options/relation;在类型 switch 里 multiple 字段**跳过标量类型检查**(防止 powers 那类问题复发)。 + +**3. typeHandler** +- array/object **不注册 resFilter**(不同于 json 会 `JSON.stringify`)——结构原样进出,前端直接拿到结构化数据。 + +### 前端 shrimp-admin + +字段渲染机制:约定式自动注册 —— `components/fields/Field.vue` → 自动注册类型 ``(首字母小写)。`vueAdmin.getFieldComponent(moduleKey, type, key)` 按 type 取组件,未知 type 兜底为 string。**范本见 `FieldTab.vue`**:用 `getFieldComponent(childField.type)` 递归渲染子字段。 + +**1. `components/fields/FieldArray.vue`(新)** 类型 `array` +- edit:列表渲染,每项 `getFieldComponent(field.item.type)` 递归 + 增/删行。 +- display:摘要(JSON 截断,或「N 项」)。 +- props 标准接口:`{field, modelValue, mode:'display'|'edit'|'filter', disabled}`。 + +**2. `components/fields/FieldObject.vue`(新)** 类型 `object` +- edit:按 `field.fields` 逐个 `el-form-item` + 递归组件(≈ FieldTab 去掉 tab 选择层)。 +- display:摘要。 + +**递归天然支持嵌套**:array 的 `item` 可以是 `{type:"object",fields:[...]}`(对象数组),object 的某字段可以是 `{type:"array",...}`,任意层级。 + +### powers 若迁移到 array(可选,非必须) +`{ key:"powers", type:"array", item:{type:"string"}, unique:true }`(集合去重)。但 powers 现用 `"powers"` 自定义类型已工作,**无强需求迁移**。 + +## 发版 +实现并前后端验证(后端 `tsc --noEmit`、前端 `vue-tsc`/build)通过后,shrimp-server + shrimp-admin 一起升 **0.7.0**,业务项目引用同步更新。 + +## 关联 +- 另一个已知待办:`field.validate`(字段级自定义验证函数,`IFieldConfig.validate`)在 `IShrimpType.ts` 定义了,但 `prepareAddItem`/`prepareEditItem` 只调了 `handler.validate`(typeHandler 级),**`field.validate` 从未被调用** —— 漏接,待补。