Files
shrimp-server/docs/array-object-field-design.md
T

5.2 KiB
Raw Permalink Blame History

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

// 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<X>.vue → 自动注册类型 <x>(首字母小写)。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 从未被调用 —— 漏接,待补。